DEV Community

Cover image for How to Secure JWT in a Single-Page Application
Nilanth
Nilanth

Posted on • Updated on • Originally published at nilanth.Medium

How to Secure JWT in a Single-Page Application

Securely make JWT based authentication in React Application.

In this article, we will see how to securely store the JWT token in a single page app for authentication.

What are all the options we have to store the token in the browser?

  1. Local storage
  2. Memory
  3. Cookie

JWT in Local Storage

Is local storage is secure to store a token? Let see now, Local storage is accessible from client-side only, so your API provider will set the JWT in the API response Authorization header as a bearer token in login or Register API if the status success. In React, we will get the JWT and store it in the local storage as below

image_1.png

image_2.png

And for the subsequent request made from the react app, the JWT is taken from local storage and set in the API request Authorization header to maintain the user session

image_3.png

Values in local storage are accessible by javascript, so any cross-site script can get the JWT from local storage and gain your account access.

image_4.png

So we should not use local storage for storing JWT if you are using, Please update your authentication architecture as local storage is not secure to store a token. Next, let's move to memory

JWT in Memory (React State)

React state variables will be assigned to default values when the app is refreshed or opened in a new tab, so if the default values are null, when the app is refreshed or opened in a new tab it will be set to null, so when we set the JWT in state variable it will disappear, so the user need to log in each time the app is refreshed or opened in a new tab or the app is closed, it will be poor User Experience. So we cannot store the JWT in the state variable.

Before moving to JWT in cookie, Let’s see about what is a cookie and its major attributes

Cookie

A cookie is another storage option available in a browser which has a expire time also, cookie also have some useful attributes to secure it from cross-site scripting (XSS) attacks. Let see what are they in detail

HttpOnly

A cookie with HttpOnly attribute is not accessible by Javascript, so we cannot get the cookie as below

let cookie= document.cookie; 
Enter fullscreen mode Exit fullscreen mode

HttpOnly cookie can be set and accessed only by the server-side script. This attribute helps to prevent cross-site scripting(XSS) attacks if it’s set with SameSite=strict.

Secure

A cookie with Secure attribute will be sent to the server only over the HTTPS request, not in an HTTP request. The Secure cookie is encrypted in request and response, so Man-in-the-middle attack is prevented by using Secure attribute with HttpOnly and SameSite=strict.

SameSite

A cookie with SameSite=strict mentions that the cookie is available only for same site origin request not for cross-site request. Now let see how to use the cookie to store JWT.

JWT in Cookie

A cookie can be set from the server-side and also in client-side, First we can see how to set and get the JWT from the cookie in the React and using the browser console.

The server set the JWT as a Bearer token in the Authorization response header, In client-side, the script has access to the token present in the header, we get the token from response header and set in the cookie as below

image_5.png

image_6.png

The cookie is set to the current domain by default and expiry date is set to 1st Jan 2021. The expiry date is based on the token validity so the token will be removed from browser cookie once the expiry date reaches.

image_7.png

The cookie needs to send as a bearer token in API request header on every request made from the client. So, for that, we can get it from the cookie using document.cookie property as below

image_8.png

document.cookie will return all cookies present against the domain, so we can use react-cookie package to get a specific cookie as below

image_9.png

As we can see that the token is set and get using the script, so we could conclude that handling JWT in the react will lead to XSS (Cross-Site Scripting) attacks same as we saw before while using local storage, but we saw two attributes earlier HttpOnly and Secure, by setting these attributes will avoid these attacks. But javascript has no access to HttpOnly attribute, Only server-side script can access HttpOnly attributes. Let see how we can set the JWT from Server Side.

As previous examples, we saw that JWT is set as Bearer token in authorization header, But handling cookie in server-side we need set the cookie in Set-Cookie header and not required to mention the token type as Bearer, we can set the JWT directly in Set-Cookie.

Here I am using Express.js to set JWT in the cookie from the server and we have set secure and HttpOnly as true to restrict the javascript access of JWT in the cookie as below

image_10.png

The token in API response Set-Cookie header will be saved to browser cookies like in below image

image_11.png

image_12.png

JWT stored in the cookie will be appended in every API request headers automatically as below images

image_13.png

image_14.png

But remember that this approach only works if the React app and the BackEnd server hosted in same domain.

Now your app is secured from Cross-Site Scripting (XSS) attacks.

Need to learn more? Feel free to connect on Twitter :)

eBook

Debugging ReactJS Issues with ChatGPT: 50 Essential Tips and Examples

ReactJS Optimization Techniques and Development Resources

Top comments (22)

Collapse
 
ecyrbe profile image
ecyrbe • Edited

Do not do this. This is wrong. Always put JWT in memory, never elsewhere. For the statement about user experience, continue reading.

You should not use jwt cookies with http.only, or whatever... this will force you to develop backend APIs that use cookies instead of bearer tokens...this make your APIs browser dependant. This is Bad, your APIs should be browser agnostic.

The solution can be to only use cookies for your autorisation endpoint (usually something like /login or /authorize ) to do single sign on if you want better user experience. This is a convenient way of doing sso, but there are other secured technologies not relying on cookies.
This authorisation endpoint should be the only endpoint using cookies. If it receives a valid cookie it returns a JWT to the caller else it will redirect the user to the authentication page. As simple as that.

All the other APIs should use the jwt stored in memory and passed as a bearer token. Nothing else.

Collapse
 
sparkydman profile image
Ugwuede Chigozie

Am interested in this your approach but I still don't get your explanation correctly. Please if you can create a snippet maybe in GitHub that will be appreciated. Thank you so much👏

Collapse
 
salmannotkhan profile image
Salman Shaikh

What if we specify both in middleware first check if authorization header exists or not and if not then check for cookies. so this way you can support both ways :)

Collapse
 
felixasante profile image
felix asante

I am also interested in your approach. if you can make your explanations clear, it will really help

Collapse
 
nhat_nam20 profile image
NhatNam20

I am interested in knowing the secured technologies are you mentioning!

Collapse
 
oguimbal profile image
Info Comment hidden by post author - thread only accessible via permalink
Olivier Guimbal • Edited

Nice article :)
I'd just add a bit of constructive critiscism (disclaimer: opininiated content ahead)

Saying that localStorage is just unsafe is not entirely true.
You CAN quite easily prevent JS libs from accessing your jwt when stored in localstorage (see my article).

Moreover, if someone with bad intentions can run js on your website, seeing your tokens stolen will be the least of your problems...

And finally, it is very easy to forget that protecting against XSS attacks with cookies will likely make your app vulnerable to CSRF attacks.

Agreed, http-only cookies may be the way to go when you're a cybersecurity ayatollah which knows what he's doing, but if you're building humble SPAs, and you dont know much about security, storing tokens in localstorage may actually be safer: You just have to trust the libraries you depend upon, that's all (while using cookies mean actively protecting your endpoints against CSRF... meaning that you must be conscious of the existence of this technique).

I wrote about "cookies vs localstorage for tokens storage" here:

Collapse
 
kamranayub profile image
Kamran Ayub • Edited

I'd recommend this series by Ben Botto on how to approach this securely.

medium.com/@benjamin.botto/secure-...

There's a few ways to handle this. In both companies I worked at, we implemented multiple tiers of token levels. Meaning that what gets stored in local storage is a low-access token. If an attacker got ahold of this, they could only access low-privilige APIs, like read only methods that didn't deal with PCI data.

For higher level access, you require the user to go through an auth flow and a temporary high-access token is issued that uses a session cookie AND the PCI based pages are server-side only.

This has the benefit of still allowing a mostly SPA architecture and you can store tokens in local storage but with added security for pages that deal with personally-identifiable information (PII) or PCI data.

For a pure SPA, the session reverse proxy approach like Ben lays out is probably needed throughout. But for the apps I work on, we do a hybrid client-server. It's still React but all PCI-compliant pages are actually bundled separately and only rendered in SSR.

Other security practices like Content Security Policy, Subresource Integrity, CORS, Same Site cookies, and CSRF prevention help mitigate browser-based attacks too.

Collapse
 
btvoidx profile image
btvoidx

Just store it in local/session storage. XSS is not an issue with modern frameworks. Moreover, if you have an xss on your website, it doesn't actually matter where you store your jwts, attacker can just send a request to your api, browser will be more than happy to add cookies to this request.

Collapse
 
dhruvindev profile image
Dhruvin

agreed

Collapse
 
shivarajnaidu profile image
yuvaraj

What if we are using separate domain for APIs ?

Can we store the cookies in our SPA when we are consuming APIs (domain y) from domain x ?

Now a days browsers blocking 3r party cookies .. if we do so it can break the site right ?

Collapse
 
ecyrbe profile image
ecyrbe

You Can setup cookies with cors by adding Access-Control-Allow-Credentials
and to explicitly setup Acess-Control-Allow-Origin to your main domain.

Collapse
 
shivarajnaidu profile image
yuvaraj

Also now days its becomes common to block third party cookies in browsers .. in that case if we use diffrent domain it may break the flow right ?

Collapse
 
shivarajnaidu profile image
yuvaraj

You mean sub domain and main domain ?

Collapse
 
atimetoremember profile image
Son Le Si • Edited

Nice article, but I have a question, If I want to design an API that is used by both web and mobile, so how can I design it for mobile?, because mobile doesn't support for cookie like the web. Hope to get reply from you and other contributors.

Collapse
 
ichavezf profile image
Eduardo Chavez

you want try secure storage, sqlite, sharedpreferences.. etc etc

Collapse
 
nilanth profile image
Nilanth

Thanks for your question.
This approach is only for web, for mobile it is completely different, we need pass the JWT as Authorization bearer token. so we need to go with separate APIs. for example api/mobile/login instead of api/web/login

Collapse
 
petrshchukin profile image
PetrShchukin • Edited

Nice article, but unfortunately your approach with JWT token in cookie won't work. You said "But remember that this approach only works if the React app and the BackEnd server hosted in same domain." It doesn't matter in this case, a requests will be sent from client browser. React server merely sends html and js code to a client machine.

Collapse
 
srikanth597 profile image
srikanth597

Session store would work great, and use refresh tokens/ expire the token every couple of mins would do the trick I believe, and also as best practice rotate the SecureKEY in Jwt provider should be followed

Collapse
 
bonarhyme profile image
Bonaventure Chukwudi

Well, I see your reasons above and I appreciate that.

If you can remove every login token from the localStorage when the user logs out or when the user closes the tab, it would be nice.

Well, I don't know much about security but according to some tutors, they recommended localStorage but they warned to clear localStorage on user logout.

Collapse
 
ichavezf profile image
Eduardo Chavez

Intenta usar sessionstorage. Segun mal no receurdo se borrar al cerrar el a navegador

Collapse
 
aymenmarouani profile image
Aymen Marouani

Thanks for the article, but I have a need where the JavaScript code have to read the token and send it in a HTTP authorization header (axios http equest), how can I achieve this feature ?

Collapse
 
edgaremmanuel profile image
DevByJESUS

If you havce store the Jwt somewhere , you can have a config that says that if the user is logged In , put the Token in the Authorization header in the Axios for each request to the Backend else do not create an Authorization header in the axios request

Some comments have been hidden by the post's author - find out more