Where to Store JWTs in the Browser

Compare JWT browser storage options: localStorage, sessionStorage, cookies, and in-memory. Understand XSS risks, CSRF protection, and the recommended approach.

Security

Detailed Explanation

Where you store JWTs in the browser significantly impacts your application's security posture. Each storage mechanism has different vulnerability profiles, and the right choice depends on your threat model and application architecture.

localStorage:

localStorage.setItem('access_token', jwt);
// Persists across tabs and browser restarts

localStorage is accessible to any JavaScript running on the same origin, which makes it vulnerable to Cross-Site Scripting (XSS) attacks. If an attacker injects malicious JavaScript (through a vulnerable dependency, unsanitized input, or a third-party script), they can read and exfiltrate the token with localStorage.getItem('access_token'). The convenience of localStorage (persistent, easy API) comes at a real security cost. It is suitable only for low-sensitivity applications or when combined with robust XSS prevention.

sessionStorage:

Similar to localStorage but scoped to a single browser tab and cleared when the tab closes. It has the same XSS vulnerability as localStorage but limits the persistence window. Not recommended for most authentication scenarios because opening a new tab requires re-authentication.

HttpOnly cookies:

Set-Cookie: access_token=eyJhbGc...; HttpOnly; Secure; SameSite=Strict; Path=/

HttpOnly cookies cannot be accessed by JavaScript, which eliminates XSS-based token theft entirely. The browser automatically sends them with every request to the matching domain. However, cookies introduce Cross-Site Request Forgery (CSRF) risks, which must be mitigated with SameSite=Strict or SameSite=Lax attributes, CSRF tokens, or custom header requirements. HttpOnly cookies are the recommended storage mechanism for security-sensitive applications.

In-memory storage:

Storing the token in a JavaScript variable (e.g., in a React context or a closure) means it cannot survive a page refresh and is only accessible to your application's JavaScript. This provides the strongest isolation from XSS but the worst persistence. It works well combined with a refresh token stored in an HttpOnly cookie: on page load, the app uses the refresh cookie to obtain a new access token and holds it in memory.

Recommended pattern:

Store refresh tokens in HttpOnly, Secure, SameSite=Strict cookies. Store access tokens in memory (a JavaScript variable). On page load, use the refresh cookie to silently obtain a new access token. This approach eliminates XSS exposure for both tokens and limits CSRF risk because the access token (which authorizes API calls) is never in a cookie.

Use Case

A fintech application stores refresh tokens in HttpOnly cookies and holds access tokens in memory, eliminating both XSS token theft and CSRF-based API abuse.

Try It — JWT Decoder

Open full tool