Modern web apps often use localStorage
for JWTs — but that’s risky.
localStorage
is accessible to JavaScript, so an XSS attack can easily steal your token.
The proper way: use HttpOnly cookies + CSRF tokens.
Here’s how to transform your existing /login
endpoint securely without breaking Kafka, Redis caching, or rate limiting.
πͺ Step-by-Step Migration Plan
Step 1: Switch from LocalStorage to HttpOnly Secure Cookie
-
Instead of returning the JWT in the response body, set it as an HttpOnly, Secure, SameSite=Strict (or Lax) cookie.
-
These cookies can’t be accessed by JavaScript — protecting against XSS.
-
No change is needed in your Kafka or Redis logic — they’ll continue working because you’re just changing how the token is delivered, not the backend authentication logic.
π‘ Kafka login notifications and Redis login limiters will remain unaffected, since they trigger before token issuance.
Step 2: Introduce a CSRF Token
-
When a user logs in, generate a CSRF token (a random UUID).
-
Store this token:
-
Option 1: in Redis (recommended if you already use Redis)
-
Option 2: in an SQL table (
csrf_tokens
)
-
-
Send this token as a non-HttpOnly cookie or via a header (so frontend can read it).
-
Frontend includes this token in every state-changing request header:
X-CSRF-TOKEN: <token>
π‘️ The backend will reject any POST/PUT/DELETE without a valid CSRF token that matches the user’s session or Redis entry.
Step 3: Secure Cookie Configuration
Update your application.properties
:
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.same-site=Strict
If your frontend and backend are on different domains:
server.servlet.session.cookie.domain=.yourdomain.com
Step 4: CORS Configuration (Critical)
When using cookies for auth:
-
You must enable credentials and disable wildcards (
*
).
Step 5: Frontend Adjustments
-
❌ Remove
localStorage
usage for JWTs. -
✅ Use
fetch
oraxios
Store only the CSRF token in memory or
sessionStorage
.-
For POST/PUT/DELETE requests
Handle 403 (CSRF error) responses gracefully — show a message like “Session expired, please refresh or re-login.”
Step 6: Optional — Add Session Mapping (for Admin Panels or Token Revocation)
If you want to track or revoke tokens:
-
Add a
session_id
column in DB or Redis mapping:
session_id -> jwt_id
On logout or admin disable, revoke by session ID.
Step 7: Test OWASP Protections
Verify:
-
✅ No JWT in
localStorage
orsessionStorage
-
✅ Cookies have
HttpOnly
,Secure
, andSameSite
flags -
✅ CSRF token mismatch returns
403
-
✅ XSS payloads can’t read cookies
-
✅ Rate limiter still blocks excessive login attempts
-
✅ Kafka still receives login notifications
⚙️ Additional Considerations
π️ Database Changes
-
Optional: Add
csrf_tokens
table or store in Redis (csrf:{sessionId} → token
).
π§ Config Updates
-
Add cookie + CORS settings to
application.properties
. -
Ensure backend sends cookies via
ResponseCookie.from()
in Spring.
π» Frontend
-
Remove token storage logic.
-
Add
withCredentials: true
in requests. -
Always attach
X-CSRF-TOKEN
header on write requests.
✅ Summary
Protection | Mechanism | Mitigates |
---|---|---|
HttpOnly cookie | JWT in secure cookie | XSS |
CSRF token | Separate token validation | CSRF |
Input sanitization (already using Jsoup) | Clean username/password | Injection |
Rate limiting (already in place) | IP-based limiter | Brute force |
Kafka login events | Audit trail | Security monitoring |
No comments:
Post a Comment