Why is storing JWTs in localStorage dangerous?
What interviewers are testing
Token storage is where authentication design meets XSS, and interviewers use it to see whether you understand JWTs as bearer credentials rather than magic strings. The strongest answers separate what the token is (signed claims), what it represents (possession equals identity), and where it lives (JavaScript-readable storage versus HttpOnly cookies), then name the trade-off each choice creates. The question also opens the door to refresh rotation and revocation, which is where senior candidates stand out.
Mental model
A JWT is a signed, base64url-encoded claim set and a bearer credential: whoever holds the string is treated as the user. localStorage is readable by every script on the origin, so any XSS turns a stored token into silent, valid-until-expiry account takeover. HttpOnly cookies move the credential out of JavaScript reach and have the browser attach it automatically, trading XSS exfiltration for CSRF exposure that SameSite and CSRF tokens must then handle.
Step-by-step solution
Step 1 of 5
Three parts, no secrets
A JWT is three base64url segments joined by dots: a header describing the algorithm, a claims payload, and a signature. Base64url is reversible encoding, not encryption, which is the first misconception this question tests. Anyone holding the token can decode the payload and read every claim — user id, role, expiry — and the animation shows exactly that, with the decoded object appearing beside the raw string. What the signature actually buys is integrity: it proves the server issued these claims and that nobody altered them, because forging a valid signature without the key is infeasible — provided the verifier pins the expected algorithm and rejects none; naive verification that trusts the token's own alg header falls to alg-confusion or unsigned-token attacks. It does not encrypt the payload and it does not identify the current holder. The distinction to say out loud in an interview is "signed, not secret" — then explain what that means for storage and trust.
// raw token: header.payload.signature
// eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1XzQ4MiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTc2MDAwMDAwMH0.9xQ...
// decoded payload (anyone can do this)
JSON.parse(atob('eyJzdWIiOiJ1XzQ4MiIsInJvbGUiOiJhZG1pbiIsImV4cCI6MTc2MDAwMDAwMH0'))
// { "sub": "u_482", "role": "admin", "exp": 1760000000 }Animation — Three parts, no secrets
Assumed — a token that proves who you are
Reality — signed claims, readable by anyone
Assumed: the token is opaque and encrypted.
Edge cases & traps
- Keeping tokens in memory only: it reduces XSS exposure but loses sessions on refresh — pair it with an HttpOnly refresh cookie or accept re-login.
- Using sessionStorage as a safer localStorage: any script on the origin still reads it for the tab's lifetime, so XSS exfiltrates just as easily.
- Long-lived access tokens in cookies: HttpOnly stops JavaScript theft, but a leaked cookie stays valid for its full lifetime — keep exp short and rotate.
- Refresh rotation without reuse detection: a stolen refresh token can be used forever — detect replay and revoke the entire token family.
- Stateless JWTs with no revocation path: logout and password changes cannot un-issue them — add a jti denylist, a token version claim, or both.