How to Store JSON Web Tokens Securely in JavaScript
JSON Web Tokens (JWTs) are an open standard for securely transmitting information between parties as a JSON object, widely used for stateless authentication in modern web applications. This article breaks down the anatomy of a JWT, analyzes the vulnerabilities associated with client-side token storage—specifically Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF)—and explains the most secure strategies for managing JWTs in JavaScript environments.
What is a JSON Web Token (JWT)?
A JSON Web Token is a compact, URL-safe token format consisting of
three parts separated by dots (.):
- Header: Contains metadata about the token, typically the signing algorithm (e.g., HMAC SHA256 or RSA) and the token type.
- Payload: Contains the claims, which are statements
about an entity (typically the user) and additional data like expiration
timestamps (
exp). - Signature: Generated by taking the encoded header, encoded payload, a secret key (or private key), and signing them using the specified algorithm to ensure data integrity.
Because the payload is merely Base64Url-encoded—not encrypted—anyone can decode and read its contents. Therefore, sensitive data such as passwords should never be placed in a JWT payload.
Common Storage Options in JavaScript and Their Risks
1.
localStorage and sessionStorage (Web Storage
API)
Web Storage is accessible by any JavaScript running on the same domain.
- Pros: Highly accessible for Single Page Applications (SPAs) and immune to CSRF attacks.
- Cons: Extremely vulnerable to Cross-Site Scripting
(XSS). If an attacker injects malicious JavaScript via a third-party
script or an unsanitized input field, they can execute
localStorage.getItem('token')and extract the token immediately.
2. Standard Cookies
JavaScript can read and write standard cookies via
document.cookie.
- Pros: Automatically sent with HTTP requests to the origin domain.
- Cons: Carries the same XSS risks as
localStoragewhile introducing vulnerability to CSRF attacks if not paired with CSRF protection mechanisms.
3. HttpOnly Cookies
An HttpOnly cookie is set by the server and cannot be
accessed by client-side JavaScript via document.cookie.
- Pros: Completely prevents token theft through XSS attacks because the browser handles the cookie automatically without exposing it to the JavaScript runtime.
- Cons: Susceptible to CSRF attacks if proper cookie flags are not configured.
The
Secure Approach: Memory Storage with HttpOnly Refresh
Tokens
The current industry standard for handling JWTs in modern JavaScript applications utilizes a two-token architecture:
1. Short-Lived Access Token in JavaScript Memory
- Store the JWT (access token) in a local JavaScript variable, closure, or state management store (e.g., React Context, Redux, Vuex).
- Set an expiration time between 5 to 15 minutes.
- Security Benefit: Because it resides solely in application memory, it is destroyed on page reload and cannot be directly extracted from storage through common XSS persistence vectors.
2.
Long-Lived Refresh Token in an HttpOnly,
Secure Cookie
- The backend issues a refresh token stored in an
HttpOnlycookie with the following security attributes:HttpOnly: Prevents client-side scripts from reading the cookie.Secure: Ensures the cookie is only transmitted over HTTPS.SameSite=StrictorSameSite=Lax: Defends against CSRF attacks by restricting when the cookie is sent on cross-site requests.
- When the in-memory access token expires, the JavaScript client makes
an API call to a
/refresh-tokenendpoint. The browser automatically attaches theHttpOnlycookie, and the server issues a new short-lived access token back to JavaScript memory.
Summary of Best Practices
- Never store sensitive JWTs in
localStorageorsessionStorageif the application handles sensitive user data. - Use short-lived access tokens stored in application memory to minimize the window of opportunity if a token is compromised.
- Use
HttpOnly,Secure,SameSitecookies exclusively for handling long-lived session or refresh tokens. - Implement token rotation on the server, issuing a new refresh token every time one is used and invalidating reuse attempts to prevent replay attacks.
- Sanitize user inputs and employ a Content Security Policy (CSP) to eliminate the underlying XSS vulnerabilities that make token extraction possible.