Frontend JWT token storage
How to store properly JWT token in a browser
Problem statement
On several applications, I noticed that JWT token is stored in local storage, session storage or through unsecured cookie. These weak techniques are vulnerable to XSS attack and token stealing (cf. session hijacking attack for more information)
Along this article, I will explore Closure, Secure Cookie and Service Worker using JQuery Javascript for the frontend and Express with Node.js to implement JWT creation and verification API.
Source code could be found at: https://github.com/elie29/jwt-token-storage
Application Proof Of Concept
The application is split into two parts: frontend and backend folders:
Each folder has its own “package.json” and should run in a separate command prompt using “npm start”.
The frontend application starts on port 4000, while the backend starts on port 3000
Once started, visit http://localhost:4000 and the application should be displayed as follows:
While the user is not logged in, Logout and Users buttons are disabled.
Closure
This technique aims to keep the token value “in-memory” inside an anonymous function (aka closure). In this way, the variable has a local scope and cannot be read or reached by any other scripts.
In the master branch, this is done, by enclosing the token in “$(document).ready” function:
Application Login
When the user click on Login button, the API (/login) is called and a JWT token is sent to the frontend:
JWT token expires in 5 minutes, so after logging in, refreshToken is called every 4 minutes to keep the token valid.
This technique is secure and very simple to implement, however, each time the user refreshes the page, the token is lost and she needs to log in again.
Secure Cookie
Another technique, widely used, consists of sending the token through a cookie with the following options:
- sameSite: true → for strict same site enforcement
- httpOnly: true → Javascript won’t be able to read the cookie
- secure: true → to send the cookie back to the server in the future if the browser have only an HTTPS connection
- maxAge: number → Should not exceed 30 minutes
The only drawback of this technique is that the cookie won’t be available cross-domain.
Service Worker
This is the most secure technique but a little bit complex. The service worker runs in a separate thread, which makes the token available even after reloading the page.
Page reload
On page reload, we activate the service worker and we check for an existing token. If it is the case, we validate the token by sending a request to the server.
Once the service worker is ready, we call refresh token to check whether the token still valid or not.
Set the token
The token should be set only through the service worker on login response so it could never be intercepted by an XSS attack or malicious script.
On logout or when error occurred, the client informs the service worker to clean the token.
It is strongly recommended that we return to the client a response without the token:
fetch method interception
Through the service worker, we listen on fetch method to inject the token when available:
N.B.: XMLHttpRequest ($.ajax) could not be intercepted through service worker so we use fetch instead
However, by using fetch, we need to pay attention to protect it against XSS payload:
Eververgreen browsers
Although the technique is secure and works on page reload, not all browsers support service worker.
Wrapping Up
Along this article, we tried to explore three techniques to store properly the token in browsers.
The implementation I provided does not use HTTPS. The purpose is just to interact with a cross-domain backend and illustrate the technique.
The complete source code is available at: https://github.com/elie29/jwt-token-storage
Each technique is implemented respectively in a specific branch: closure, cookie and service-worker.
Don’t hesitate to give me your feedback, or contact me on Twitter @elie_nehme