86

I am using React SPA, Express, Express-session, Passport, and JWT. I'm confused about some of the different client-side storage options to store tokens: Cookies, Session, and JWT / Passport.

Do tokens have to be stored in cookies, even if I can store them in req.sessionID?

Many websites use cookies to store shopping cart tokens. So far I have stored shopping cart data based on the session ID without adding any cookies.

So when users visit my website, I will match it with their req.sessionID and then retrieve the data in the database like shopping carts and user session.

Do I need to store cookies? I can access it via req.sessionID to get the data needed.

And the second

I have made authentication using a passport-google-oauth20.After I successfully login, the data is saved into the session. and to send it to the client I have to send it via the URL query ?token='sdsaxas'.

in this case I get a lot of difference of opinion. someone saved it into local storage and someone saved it into cookies by converting it to a token using JWT.

 jwt.sign(
        payload,
        keys.jwt.secretOrPrivateKey, 
        {
            expiresIn:keys.jwt.expiresIn // < i dont know what is this expired for cookies or localstorage ?
        }, (err, token) => {

            res.redirect(keys.origin.url + "?token=" + token);
        });

Can I indeed store everything related to the session by using sessionID (without cookies or localstorage)?

Only by doing fetch once or every page refresh and retrieving the data and then saved into redux because I use React SPA.

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
Faris Dewantoro
  • 1,597
  • 4
  • 17
  • 31
  • cookies and localstorage are used to persist sessions in the client browser. You can use them to re-authenticate a returning user. I am not familiar with the sessionID key, but I must assume it is set by the passport middleware into the express req object based on your queryString `?token=` . You may be storing this as a global variable in your application and will be lost if the user closes the browser. use cookies or localstorage if you want to store this token value for later. Or you could just make the user login again and save the trouble altogether! – Dhananjai Pai Jan 18 '19 at 16:57
  • I am just looking for references and many differences that make me confused @Quentin maybe you can explain? – Faris Dewantoro Jan 18 '19 at 17:00
  • 1
    Possible duplicate of [What is the difference between localStorage, sessionStorage, session and cookies?](https://stackoverflow.com/questions/19867599/what-is-the-difference-between-localstorage-sessionstorage-session-and-cookies) – olegario Jan 18 '19 at 17:00
  • I'm just confused about using cookies on shopping carts and user authentication to make it safe – Faris Dewantoro Jan 18 '19 at 17:04
  • unless you have a specific need, just use `localStorage` for storing `JWT`s. Most js libraries or framework plugins for authentication do that already. – Daniel Jan 18 '19 at 17:06
  • yes but I am still confused about how to connect localstorage to a session? in my code `jwt.sign()` U can see it has expired and session time also has an expiration time how can this session and local storage be connected so when the session runs out the localstore disappears? @Daniel @Dhanajai Pai – Faris Dewantoro Jan 18 '19 at 17:10

4 Answers4

114

This answer is based on the stateless approach and therefore it doesn't talk about the traditional session management

You have asked two altogether different questions:

  1. Shopping cart - which is more related to business functionality
  2. OAuth 2 & JWT - which is related to security and authentication

As a user of an ecommerce website, I'd expect that any item I add to my shopping cart from my mobile device while commuting to my workplace, should be available in the cart when I login to the website from my PC after reaching home. Therefore, the cart data should be saved in the back-end DB and linked to my user account.

When it comes to authentication using OAuth 2.0, the JWT access token and / or refresh token need to be stored somewhere in the client device, so that once the user authenticates himself by providing login credentials, he doesn't need to provide his credentials again to navigate through the website. In this context, the browser local storage, session storage and cookies are all valid options. However, note that here the cookie is not linked to any session on the server side. In other words, the cookie doesn't store any session id. The cookie is merely used as a storage for access token which is passed to the server with every http request and the server then validates the token using the digital signature to ensure that it is not tampered and it is not expired.

Although all three storage options for access and / or refresh tokens are popular, cookie seems to be the most secured option when used in the correct way.

To understand this better, I recommend you read this and this along with the OAuth 2.0 specification.

Update On 16-Feb-2019

I said earlier that cookie seems to be the most secured options. I'd like to further clarify the point here.

The reason I think browser localStorage and sessionStorage do not provide enough security for storing auth tokens are as follows:

  1. If XSS occurs, the malicious script can easily read the tokens from there and send them to a remote server. There on-wards the remote server or attacker would have no problem in impersonating the victim user.

  2. localStorage and sessionStorage are not shared across sub-domains. So, if we have two SPA running on different sub-domains, we won't get the SSO functionality because the token stored by one app won't be available to the other app within the organization. There are some solutions using iframe, but those look more like workarounds rather than a good solution. And when the response header X-Frame-Options is used to avoid clickjacking attacks with iframe, any solution with iframe is out of question.

These risks can, however, be mitigated by using a fingerprint (as mentioned in OWASP JWT Cheat Sheet) which again in turn requires a cookie.

The idea of fingerprint is, generate a cryptographically strong random string of bytes. The Base64 string of the raw string will then be stored in a HttpOnly, Secure, SameSite cookie with name prefix __Secure-. Proper values for Domain and Path attributes should be used as per business requirement. A SHA256 hash of the string will also be passed in a claim of JWT. Thus even if an XSS attack sends the JWT access token to an attacker controlled remote server, it cannot send the original string in cookie and as a result the server can reject the request based on the absence of the cookie. The cookie being HttpOnly cannot be read by XSS scripts.

Therefore, even when we use localStorage and sessionStorage, we have to use a cookie to make it secured. On top of that, we add the sub-domain restriction as mentioned above.

Now, the only concern about using a cookie to store JWT is, CSRF attack. Since we use SameSite cookie, CSRF is mitigated because cross-site requests (AJAX or just through hyperlinks) are not possible. If the site is used in any old browser or some other not so popular browsers that do not support SameSite cookie, we can still mitigate CSRF by additionally using a CSRF cookie with a cryptographically strong random value such that every AJAX request reads the cookie value and add the cookie value in a custom HTTP header (except GET and HEAD requests which are not supposed to do any state modifications). Since CSRF cannot read anything due to same origin policy and it is based on exploiting the unsafe HTTP methods like POST, PUT and DELETE, this CSRF cookie will mitigate the CSRF risk. This approach of using CSRF cookie is used by all modern SPA frameworks. The Angular approach is mentioned here.

Also, since the cookie is httpOnly and Secured, XSS script cannot read it. Thus XSS is also mitigated.

It may be also worth mentioning that XSS and script injection can be further mitigated by using appropriate content-security-policy response header.

Other CSRF mitigation approaches

  1. State Variable (Auth0 uses it) - The client will generate and pass with every request a cryptographically strong random nonce which the server will echo back along with its response allowing the client to validate the nonce. It's explained in Auth0 doc.
  2. Always check the referer header and accept requests only when referer is a trusted domain. If referer header is absent or a non-whitelisted domain, simply reject the request. When using SSL/TLS referrer is usually present. Landing pages (that is mostly informational and not containing login form or any secured content) may be little relaxed ​and allow requests with missing referer header.
  3. TRACE HTTP method should be blocked in the server as this can be used to read the httpOnly cookie.
  4. Also, set the header Strict-Transport-Security: max-age=; includeSubDomains​ to allow only secured connections to prevent any man-in-the-middle overwrite the CSRF cookies from a sub-domain.
starball
  • 20,030
  • 7
  • 43
  • 238
Saptarshi Basu
  • 8,640
  • 4
  • 39
  • 58
  • Not that I disagree with the general answer, but if one succeeds in doing an XSS penetration, then they don't need the token. – Kaiido Feb 16 '19 at 14:35
  • 2
    @Kaiido Thanks for the feedback. But with this approach the XSS has to do whatever harm it intends to do from the user's browser only. Remote impersonation is far less likely. – Saptarshi Basu Feb 16 '19 at 14:44
  • I think you misunderstood HttpOnly in cookies. HttpOnly means that javascript can't read the "document.cookie" property. However, javascript http requests STILL send the cookie value in the requests headers to the server. – Basil Musa May 22 '20 at 21:28
  • @BasilMusa Thank you for writing feedback. I don't think I meant anything different from what you said, but wonder which statement made you think I misunderstood `HttpOnly` – Saptarshi Basu May 23 '20 at 01:16
  • "Thus even if an XSS attack sends the JWT access token to an attacker controlled remote server, it cannot send the original string in cookie and as a result the server can reject the request based on the absence of the cookie." << There is no 'absence of the cookie' taking place, the cookie will be sent by the XSS malicious script to the server. – Basil Musa May 23 '20 at 21:58
  • 3
    @BasilMusa There are two things happening here - 1. The cookie will not travel with the HTTP request to the attacker's server because the cookie domain attribute will not match with the attacker's server domain and because the cookie is a `SameSite` cookie. 2. Now, since the cookie is also `httpOnly`, the XSS javascript won't be able to read it from the cookie and send the fingerprint value in the AJAX request. – Saptarshi Basu May 24 '20 at 02:07
  • Can someone still make XSS attack if i encode and decode my data in client side and block extra symbols in the backend? If still yes how do we deal with cross site requests then? – Kyoko Sasagava Jun 17 '21 at 09:07
  • Note that `SameSite` does not fully protect you from CSRF. Imagine a forum where you can leave a comment with a link like "https://thisforum.com/account/delete". You can still have CSRF if you're able to embed a link or a form on the site itself. – kszafran Feb 19 '22 at 13:22
19

LocalStorage/SessionStorage is vulnerable to XXS attacks. Access Token can be read by JavaScript.

Cookies, with httpOnly, secure and SameSite=strict flags, are more secure. Access Token and its payload can not be accessed by JavaScript.

BUT, if there is an XSS vulnerability, the attacker would be able to send requests as the authenticated user anyway because the malicious script does not need to read the cookie value, cookies could be sent by the browser automatically.

This statement is true but the risks are different.

With cookies, the access token is still hidden, attackers could only carry out “onsite” attacks. The malicious scripts injected into the web app could be limited, or it might not be very easy to change/inject more scripts. Users or web apps might need to be targeted first by attackers. These conditions limit the scale of the attack.

With localStorage, attackers can read the access token and carry out attacks remotely. They can even share the token with other attackers and cause more serious damage. If attackers manage to inject malicious scripts in CDNs, let’s say google fonts API, attackers would be able to siphon access token and URLs from all websites that use the comprised CDN, and easily find new targets. Websites that use localStorage are more easily to become targets.

For the sake of arguments

A pen-testing might flag your use of localStorage for sensitive data as a risk.

If it was ok for JavaScript to read access token from localStorage from an XSS attack, why do you think the httpOnly flag is still recommended by everyone.

Recommendation from OWASP

Do not store session identifiers in local storage as the data is always accessible by JavaScript. Cookies can mitigate this risk using the httpOnly flag.

https://medium.com/@coolgk/localstorage-vs-cookie-for-jwt-access-token-war-in-short-943fb23239ca

Dan
  • 371
  • 3
  • 6
3

HTTP is a stateless protocol. Read that answer for more detail, but essentially that means that HTTP servers, such as your web server, do not store any information about clients beyond the lifetime of one request. This is a problem for web apps because it means you can't remember which user is logged in.

Cookies were invented as the solution to this. Cookies are textual data that the client and server send back and forth on every request. They allow you to effectively maintain application state data, by having the client and server agree on what they remember each time they communicate.

This means, fundamentally, you cannot have a session without a cookie. There must be a cookie that stores at least the session ID, so that you can find out which user is currently logged into your app by looking up the session. This is what express-session does: the documentation for the main session method explicitly notes that the session ID is stored in a cookie.

so my question is do I need to store cookies?because I can access it via req.sessionID to get the data needed.

You don't need to store cookies. express-session will do this for you. Your application as a whole does need to store a cookie; without it, you wouldn't have a req.sessionID to look up.

Effective Robot
  • 366
  • 1
  • 6
  • Serverside session is not managed in this type of applications. Instead cookie is used to store the auth token and not the session id. Serverside session is difficult to scale and we are moving away from that is a microservice based architecture. – Saptarshi Basu Feb 15 '19 at 05:34
  • 1
    @SaptarshiBasu The documentation for express-session explicitly says it _does_ use server-side sessions, just storing the session ID in a cookie. – Effective Robot Feb 15 '19 at 14:16
-7

According to my experience, just store token in localStorage.

localStorage:

it can store information up tp 5MB. You do not need to ask user's permission to store token in localStorage.

The only concern is that whether the target device support localStorage api.

Check here: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage

It is widely supported. But according to my experience, if you have an ios app, and there is a html page in this app which ask the user to store token (also called webview), the localStorage api cannot be recognized and throw an error.

The solution is simply i just put token in url and transfer it every time. In webview, url is not visible.

Cookie:

It is a very old style to store info locally. Storage in cookie is relatively small and you need to ask user's permission in order to store token in cookie.

Cookies are sent with every request, so they can worsen performance (especially for mobile data connections). Modern APIs for client storage are the Web storage API (localStorage and sessionStorage) and IndexedDB. (https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)

Do not store token in sessionStorage or redux.

Data stored in sessionStorage will be lost if the tab is closed. If a user accidentally closed a tab, the token is lost and the server will not be able to identify the current user. Token stored in redux is not different to be stored in other js files. redux store is just another js file. information stored in redux get lost for every page refresh.

In conclusion,

most of the time, token is stored in localStorage if using a modern style. In certain scenarios, you can store token in cookie and may be put in url sometimes. But never store in session.

Hope it helps.

MING WU
  • 2,962
  • 1
  • 12
  • 16
  • 1
    You cannot use localstorage across sub domains which is a common requirement. Localstorage is vulnerable to XSS attack. And when you store your tokens in URLs, security goes out of the window. Cookie is not a old way, it's very much common to use cookie and it can give the appropriate level of security when used in the right way. It's about storing a auth token and not a 5MB image, so storage space doesn't matter – Saptarshi Basu Feb 15 '19 at 05:20
  • 3
    using cookies make you exposed to CSRF attacks while localStorage does not. https://stackoverflow.com/questions/35291573/csrf-protection-with-json-web-tokens/35347022#35347022. Also check the link https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies, it says localStorage is the modern api. What if users do not give you permission to use cookies? About subdomain: there are methods to solve it. check here: https://stackoverflow.com/questions/4026479/use-localstorage-across-subdomains.For SPA, make a subdomain a component, there is one html. Different urls are just different js files. – MING WU Feb 15 '19 at 05:57
  • CSRF is protected using an additional CSRF cookie along with the auth token cookie. Localstorage is a modern api for client side storage, just it doesn't provide enough security for auth token. Still there are app that do use localstorage for auth token, but it cannot be recommended so definitively. Regarding the iframe hack, do you really like that workaround? – Saptarshi Basu Feb 15 '19 at 06:07