One of the first questions developers face when implementing an authentication flow is:
Where should I store the JWT?
After all, your application needs easy access to the token so it can send it in the Authorization header when making authenticated requests.
The two most common options are:
- Local Storage
- HttpOnly Cookies
Let's compare them.
Option 1: Local Storage
localStorage is a browser storage API that allows JavaScript to store and retrieve data.
Example:
localStorage.setItem("token", jwt);
The biggest advantage is convenience.
Your token is easy to access from anywhere in your frontend application.
However, that convenience comes with an important security trade-off.
Since JavaScript has full access to Local Storage, any JavaScript running on your page can also read your token.
If your application is vulnerable to Cross-Site Scripting (XSS) and an attacker manages to inject malicious JavaScript, they can simply read the token and send it to their own server.
That's one of the main reasons security professionals discourage storing JWTs in Local Storage.
But security isn't the only drawback.
Because Local Storage only exists in the browser, it isn't available during server-side rendering.
That means if your authentication relies on a token stored in Local Storage, your authenticated requests typically have to be made from the client.
For frameworks like Next.js, this means giving up many of the benefits of:
- Server Components
- Server-side rendering (SSR)
- Server-side data fetching
Simply put, the server cannot access Local Storage.
Option 2: HttpOnly Cookies
A much more secure option is storing the token inside an HttpOnly Cookie.
The key advantage is that JavaScript cannot read HttpOnly cookies.
Even if an attacker successfully exploits an XSS vulnerability, they still can't directly access the token stored inside the cookie.
This provides a significant security improvement.
Another major benefit is that cookies are automatically included with requests to your server.
That means the token is available on both:
- The client
- The server
This is especially valuable in frameworks like Next.js, where Server Components and SSR often need access to the authenticated user's session.
Is HttpOnly Cookie always better?
Not necessarily.
Like most engineering decisions, it's a trade-off.
Local Storage
Pros
- Very simple to implement
- Easy to access from JavaScript
- Works well for purely client-side applications
Cons
- Vulnerable to XSS attacks
- Not available during server-side rendering
- Forces authentication logic to stay on the client
HttpOnly Cookies
Pros
- Much more resistant to XSS attacks
- Available to both the client and the server
- Works naturally with SSR and Server Components
Cons
- More complex to implement
- Requires proper cookie configuration (
HttpOnly,Secure,SameSite) - You also need to consider CSRF protection, since cookies are automatically sent with requests
So... which one should you choose?
For modern web applications—especially those built with frameworks like Next.js—HttpOnly Cookies are generally the recommended approach.
They provide stronger security, work seamlessly with server-side rendering, and align better with modern authentication architectures.
Local Storage is easier to use, but that convenience comes with meaningful security and architectural limitations.
This is the third post in my Frontend Security series.
Follow me to stay updated with the next episodes of my Frontend Security series
Top comments (0)