76

I'm trying to implement JWT in my authentication system and I have a few questions. To store the token, I could use cookies but it's also possible to use localStorage or sessionStorage.

Which would be the best choice?

I have read that JWT protects the site from CSRF. However, I can't imagine how that would work assuming I save the JWT token in cookie storage.

How would it then protect from CSRF?

Update 1
I saw some usage samples like the following:

curl -v -X POST -H "Authorization: Basic VE01enNFem9FZG9NRERjVEJjbXRBcWJGdTBFYTpYUU9URExINlBBOHJvUHJfSktrTHhUSTNseGNh"

How can I implement that when I make a request to server from the browser? I also saw that some implement the token in the URL:

http://exmple.com?jwt=token

If I would make a request via AJAX then I could set an header like jwt: [token] and then I could read the token from header.

Update 2

I installed the Advanced REST Client Google Chrome extension and was able to pass the token as a custom header. Is it possible to set this header data via Javascript when making a GET request to the server?

João Angelo
  • 56,552
  • 12
  • 145
  • 147
softshipper
  • 32,463
  • 51
  • 192
  • 400

5 Answers5

172

Choosing the storage is more about trade-offs than trying to find a definitive best choice. Let's go through a few options:

Option 1 - Web Storage (localStorage or sessionStorage)

Pros

  • The browser will not automatically include anything from Web storage into HTTP requests making it not vulnerable to CSRF
  • Can only be accessed by Javascript running in the exact same domain that created the data
  • Allows to use the most semantically correct approach to pass token authentication credentials in HTTP (the Authorization header with a Bearer scheme)
  • It's very easy to cherry pick the requests that should contain authentication

Cons

  • Cannot be accessed by Javascript running in a sub-domain of the one that created the data (a value written by example.com cannot be read by sub.example.com)
  • ⚠️ Is vulnerable to XSS
  • In order to perform authenticated requests you can only use browser/library API's that allow for you to customize the request (pass the token in the Authorization header)

Usage

You leverage the browser localStorage or sessionStorage API to store and then retrieve the token when performing requests.

localStorage.setItem('token', 'asY-x34SfYPk'); // write
console.log(localStorage.getItem('token')); // read

Option 2 - HTTP-only cookie

Pros

  • It's not vulnerable to XSS
  • The browser automatically includes the token in any request that meets the cookie specification (domain, path and lifetime)
  • The cookie can be created at a top-level domain and used in requests performed by sub-domains

Cons

  • ⚠️ It's vulnerable to CSRF
  • You need to be aware and always consider the possible usage of the cookies in sub-domains
  • Cherry picking the requests that should include the cookie is doable but messier
  • You may (still) hit some issues with small differences in how browsers deal with cookies
  • ⚠️ If you're not careful you may implement a CSRF mitigation strategy that is vulnerable to XSS
  • The server-side needs to validate a cookie for authentication instead of the more appropriate Authorization header

Usage

You don't need to do anything client-side as the browser will automatically take care of things for you.

Option 3 - Javascript accessible cookie ignored by server-side

Pros

  • It's not vulnerable to CSRF (because it's ignored by the server)
  • The cookie can be created at a top-level domain and used in requests performed by sub-domains
  • Allows to use the most semantically correct approach to pass token authentication credentials in HTTP (the Authorization header with a Bearer scheme)
  • It's somewhat easy to cherry pick the requests that should contain authentication

Cons

  • ⚠️ It's vulnerable to XSS
  • If you're not careful with the path where you set the cookie then the cookie is included automatically by the browser in requests which will add unnecessary overhead
  • In order to perform authenticated requests you can only use browser/library API's that allow for you to customize the request (pass the token in the Authorization header)

Usage

You leverage the browser document.cookie API to store and then retrieve the token when performing requests. This API is not as fine-grained as the Web storage (you get all the cookies) so you need extra work to parse the information you need.

document.cookie = "token=asY-x34SfYPk"; // write
console.log(document.cookie); // read

Additional Notes

This may seem a weird option, but it does has the nice benefit that you can have storage available to a top-level domain and all sub-domains which is something Web storage won't give you. However, it's more complex to implement.


Conclusion - Final Notes

My recommendation for most common scenarios would be to go with Option 1, mostly because:

  • If you create a Web application you need to deal with XSS; always, independently of where you store your tokens
  • If you don't use cookie-based authentication CSRF should not even pop up on your radar so it's one less thing to worry about

Also note that the cookie based options are also quite different, for Option 3 cookies are used purely as a storage mechanism so it's almost as if it was an implementation detail of the client-side. However, Option 2 means a more traditional way of dealing with authentication; for a further read on this cookies vs token thing you may find this article interesting: Cookies vs Tokens: The Definitive Guide.

Finally, none of the options mention it, but use of HTTPS is mandatory of course, which would mean cookies should be created appropriately to take that in consideration.

João Angelo
  • 56,552
  • 12
  • 145
  • 147
  • 3
    Superb explanation! I too feel option 1 would be best atleast for my requirements for an intranet site but public web apps could be tricky. – Anjan Biswas Apr 24 '17 at 04:50
  • came here for option 2! seems to me like the best one, although not the most semantically correct – Doug Sep 26 '20 at 04:22
  • 3
    From what I can see with the introduction of the [`SameSite`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) attribute of the `Set-Cookie` header option 2 is [getting](https://caniuse.com/?search=samesite) invulnerable to CSRF. Especially after making `Lax` the default value. In other words option 2 is probably safer, if not right now, then at least in the near future. And it's not clear what exactly you meant by "If you're not careful you may implement a CSRF mitigation strategy that is vulnerable to XSS." – x-yuri Feb 07 '21 at 18:22
30

[EDIT] This answer is the accepted one, however the response from João Angelo is way more detailed and should be considered. One remark though and because the security pratices evolved since Nov. 2016, the Option 2 should be implemented in favour of the Option 1.

Look at this web site: https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/

If you want to store them, you should use the localStorage or sessionStorage if available or cookies. You should also use the Authorization header, but instead of Basic scheme, use the Bearer one:

curl -v -X POST -H "Authorization: Bearer YOUR_JWT_HERE"

With JS, you could use the following code:

<script type='text/javascript'>
// define vars
var url = 'https://...';

// ajax call
$.ajax({
    url: url,
    dataType : 'jsonp',
    beforeSend : function(xhr) {
      // set header if JWT is set
      if ($window.sessionStorage.token) {
          xhr.setRequestHeader("Authorization", "Bearer " +  $window.sessionStorage.token);
      }

    },
    error : function() {
      // error handler
    },
    success: function(data) {
        // success handler
    }
});
</script>
Spomky-Labs
  • 15,473
  • 5
  • 40
  • 64
  • 1
    I want to replace cookie session storage through jwt. Now when I would make a GET request to the server, how can I give this token with? Yes I would save the token in the session- or localstorage, but the difficulty is, how can I send the token every request(also GET too) to the server. I know when I use ajax request I can request headedr, but when I do not, how can I send the token to the server? – softshipper Oct 14 '14 at 09:22
  • I updated my answer with som (not tested) lines of code. You just have to set the authorization header in each request and let the server verify that the JWT is valid – Spomky-Labs Oct 14 '14 at 09:54
  • My question is, how can I do without ajax, it seems not to be possible, right? – softshipper Oct 14 '14 at 10:02
  • You can store your JWT in a cookie. – Spomky-Labs Oct 14 '14 at 10:06
  • I want to prevent cookie, because some it is not mobile friendly, I read about it. It is true? – softshipper Oct 14 '14 at 10:09
  • 1
    Cookies are supported on mobile browser. As on browsers on computers, there may be some limitations due to the browser itself or the configuration of the browser (3rd party cookies may be refused for example). See http://stackoverflow.com/questions/4489733/cookies-on-mobile-phone – Spomky-Labs Oct 14 '14 at 14:55
  • Then it also save, to save token in the cookie? – softshipper Oct 14 '14 at 18:22
  • And if you run into "undefined" issues trying to get to $window.sessionStorage.token or $(window).sessionStorage.token, then just do: window.sessionStorage.token – Houdini Sutherland Sep 22 '15 at 17:24
  • 1
    What did you mean with "is recommended to not store JWT". How you send your JWT token in your next requests? You must store the token in either localStorage, sessionStorage or a cookie. – gabrielgiussi Jan 25 '16 at 18:09
19

As of 2021 things evolved a bit with the introduction of the SameSite: Lax/Strict option for cookie on most nowdays browsers

So to elaborate on João Angelo answer, i would say the most secure way is now:

Store the JWT in a cookie with the following options

  • HttpOnly
  • Secure
  • SameSite: Lax or Strict

This will avoid XSS and CSRF both together

fadomire
  • 1,865
  • 1
  • 15
  • 23
12

This blog post has excellent side by side comparison of browser storage vs. cookies and tackles every potential attack in each case. https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage/

The shorter answer / spoiler: cookies and add xsrf token in the jwt. Detailed explanation in the blog post.

Carlos Arrastia
  • 179
  • 2
  • 7
  • That particular solution still has vulnerabilities that should be noted. The article posted moderates the disqus comments that were in response to the article. Here is the discussion that goes more in depth (without the political company agenda) - https://disqus.com/home/discussion/stormpath/where_to_store_your_jwts_cookies_vs_html5_web_storage_stormpath_user_management_api/ – seasick May 16 '16 at 00:17
  • 2
    The Disqus discussion that you linked appears to be gone. Can you please elaborate on the shortcomings of the StormPath approach and how to overcome them? – Darwin Airola Sep 29 '16 at 22:56
5

You should NEVER, EVER store a JWT outside of memory.

If you want to persist a JWT for the duration of a long session (say 1 hour when a token has an expiry of only 15 mins), silently log the users again in the background whenever the token is about to be expired.

If you want to persist a JWT across sessions, you should use a refresh token. Which, BTW, is most of the time used for above purpose as well. You should store it in an HttpOnly cookie (well, more precisely, a sever sets is via Set-Cookie header, the front end calls the /refresh_token API endpoint.)

A refresh token, BTW, is the least of the evils; to complement it, you should ensure you are following best practices to mitigate XSS.

localStorage, sessionStorage, and cookies all have their vulnerabilities.

This is the best guide i have ever read on JWTs: https://blog.hasura.io/best-practices-of-using-jwt-with-graphql/

T. Dayya
  • 690
  • 11
  • 12
  • that guide is golden – mouchin777 Feb 11 '20 at 09:45
  • - JWT as an id_token is like your user credentials - JWT as an access_token is like your session token The most secure option is in-memory. [Checkout this for a deep dive](https://mannharleen.github.io/2020-03-19-handling-jwt-securely-part-1/) – human Mar 19 '20 at 11:24