91

When my users are authenticated they receive an authentication token, I need to use this authentication token to authorize some asp.net WebAPI calls. To do this I need to add the token to the head of that call, so I need the token accessible from the users browser. I think that storing the token in a cookie isn't the safest way, so what is the safest way to store that token and still accessible in my javascript to make API call's?

James
  • 1,698
  • 3
  • 13
  • 18
jfamvg
  • 1,013
  • 1
  • 8
  • 5
  • http://ceur-ws.org/Vol-313/paper9.pdf please read this.. If your trying to implement something against best practice that is not generic. Then your will have problems later down the line supporting your client. – Rio Hazuki Feb 03 '15 at 10:33
  • I think using a SSL certificates should do your job, there publications here explaining their usages and what they do: http://www.rfc-base.org/rfc-6101.html. – Rio Hazuki Feb 03 '15 at 10:29
  • 7
    In what way will SSL help secure a token stored on the client? – James Feb 03 '15 at 10:30
  • 1
    Session cookie. Don't overthink it, these things were made for a reason. – Andrew Hoffman Oct 21 '15 at 00:02
  • 3
    "I think that..." When the rest of the world relies on cookies for storing such tokens, maybe you should explain your thinking so we might all be better informed and have a chance of coming up with a solution which addresses your concerns? – symcbean Jul 30 '16 at 22:26

6 Answers6

72

There are two ways you can save authentication information in the browser:

  1. Cookies
  2. HTML5 Web Storage

In each case, you have to trust that browsers are implemented correctly, and that Website A can't somehow access the authentication information for Website B. In that sense, both storage mechanisms are equally secure. Problems can arise in terms of how you use them though.

If you use cookies:

  • The browser will automatically send the authentication information with every request to the API. This can be convenient so long as you know it's happening.
  • You have to remember that CSRF is a thing, and deal with it.

If you use HTML5 Web Storage:

  • You have to write Javascript that manages exactly what authentication information is sent to the API.

A big practical difference people care about is that with cookies, you have to worry about CSRF. To handle CSRF properly, you need an additional "synchronizer token".

All-in-one web frameworks (like Grails, Rails, probably asp.net) usually provide an easy way to enable CSRF protection, and automatically add synchronizer token stuff in your UI. But if you're writing a UI using a client-side-only web framework (like AngularJS or BackboneJS), you're going to have to write some Javascript to manage the synchronizer token. So in that case you might as well just go with the HTML5 Web Storage approach and only worry about one token.

Chris Clark
  • 916
  • 6
  • 5
  • 2
    You could also split the token into two parts (see https://jcbaey.com/authentication-in-spa-reactjs-and-vuejs-the-right-way). One part in a http-only cookie, one in the local storage. That way, CSRF is not possible, and it cannot be stolen via XSS. – Franz Deschler Nov 09 '20 at 11:43
10

The best way to protect your access token is to not store it client-side at all.

How does that work? Well at the point of generating the access token, generate some other cryptographically secure PRNG (which you map to the access token on the server), map this to the users session ID and return this to the client instead.

This will reduce the attack area because what you are now returning is a token tied to a session, and both would be required in order to authorise the token. When the session expires so does the token, naturally, and the user would be forced to re-authenticate thus generating a new access token.

It's still not 100% secure, however, you need to weigh up the possibility of something like that happening within a users session. Based on that, you can then tweak your session/token expiry times to suit, although you also need to be wary of usability - you don't want to be expiring sessions after 5 minutes as having to log back in constantly can be tedious (or maybe you can, depends on the type of application).

James
  • 1,698
  • 3
  • 13
  • 18
  • Thanks for your awnser, but I need to include the token in the header of my AJAX API call so I need the full token accessible in my javascript. – jfamvg Feb 03 '15 at 12:11
  • @jfamvg I'm not suggesting you don't store *a* token on the client, I'm saying don't store the *private* access token there. Generate a public token and send that instead. – James Feb 03 '15 at 15:43
  • I have to send the literal token to the WebAPI, so I need to make an additional call to the server to get the token form the private token to send that to my WebAPI. – jfamvg Feb 03 '15 at 16:03
  • 14
    I don't understand the distinction between a public and private token. In OAuth a token is a token. If you can use it to make a request to WebAPI then what protection are you offering here? Are you assuming there is some information in the token that needs to be protected? If you are using Katana's OAuth provider, it handles that by encrypting the claims that make up the token. – Ed Greaves Jun 14 '16 at 16:00
  • @EdGreaves was a while ago now but based on the question, the assumption was the user had a "sensitive" token that they didn't feel comfortable storing on the client. My suggestion was simply don't store the token client-side at all if that's the case, store what would be effectively a public key that could be mapped to a private one on the server. If the public key was ever compromised there would be no concern about internal data being leaked. You still of course have the issue that the key could be used for malicious purposes but that's a whole different issue. – James Jul 05 '17 at 09:32
  • @James Isn't that just a session then? What's the point of storing the token on the server and having some key / ID on the client side to reference it? – Jack Aug 17 '18 at 09:36
  • 2
    Isn't an access token just a cryptographically secure randomness, though? – Lazar Ljubenović Nov 06 '18 at 15:09
  • 1
    @LazarLjubenović well that all depends on _how_ that token is generated, when using services like OAuth etc. then yes you can pretty much guarentee the token coming back is safe to be stored on the client (to an extent). – James Nov 06 '18 at 15:54
1

ARMOR is designed specifically to meet this requirement. The Anti-forgery token can be stored anywhere on the UI that you see fit. The library comes equipped with a custom JavaScript component that extracts the token from the UI and automatically includes it in AJAX headers.

Here is a tutorial on the subject, and here is the GitHub repo.

Paul Mooney
  • 151
  • 3
1

I'd store the token in a cookie with the following three flags: 1. Secure: transmit over https 2. HttpOnly: client-side JS cannot read it (XSS protection) 3. SameSite (either Lax or Strict): CSRF protection

In this way you are immune to XSS and CSRF. You only need to be careful about SameSite, as it is relatively new and only got recently supported by the 4 major browsers (Chrome, FF, Safari, Edge) see: https://caniuse.com/#feat=same-site-cookie-attribute

For more info regarding cookie hardening see https://odino.org/security-hardening-http-cookies/

fox
  • 11
  • 1
0

Alex's blog must be informative.

A server dies every time someone implements OAuth in a single page is web app. Stop the genocide! Use a server side proxy! Act now!

https://github.com/alexbilbie/alexbilbie.github.com/blob/master/_posts/2014-11-11-oauth-and-javascript.md

briankip
  • 113
  • 5
zono
  • 185
  • 8
  • 5
    The proposed solution in that article seems unhelpful. Assuming the attacker has access to a user's client state (tokens, cookies, etc.) then the attacker can simply send the same request to the proxy server: `GET /ajax/resource/123 HTTP/1.1 Cookie: Host: example.com`. If he means "use cookies instead of local storage", he should say so more clearly. – BudgieInWA Nov 18 '15 at 02:40
  • That article does not make sense. How will the proposed proxy server differentiate between the "real user" and the "attacker"? All http requests can be faked. CSRF just needs one-extra http call to obtain csrf token. Only solution to author's problem can be captcha and rate-limiting. Please explain if I am wrong. – cnvzmxcvmcx Aug 07 '16 at 17:56
0

You need to send the token to server in every requset. So it doesn't matter you store it in cookie or html 5 storage. Both are secure storages and eveyone who has access the client machine has access to the token too anyway.

But I recommend do not use the submitted token in cookie on your server to prevent CSRF attack. I think it is good idea to manually include the token in every request you make whenever required.

Using cookie also makes the request header bigger which is not appropriate.

  • 1
    This is conflicting. How can you include a token in every request and at the same time not include it in the `Cookie:` HTTP header? Well, you could make your own `X-Something:` header, but that is probably worse since you need to maintain the code for the storage of the content of the header. – grochmal Dec 16 '16 at 01:33
  • You are right grochmal. It is possible for my web apps as I always just invoke api call through ajax request. So my method can not be implemented in other senarios. – Mohammad Nikravan Dec 16 '16 at 09:18