How to store JWTs in browser-based apps

I went through the following options:

  1. Web Storage (localStorage or sessionStorage)
  2. HTTP-only cookie
  3. Javascript accessible cookie ignored by server-side

in what can be described as possibly my longest answer on StackOverflow so I’ll just transcribe the conclusions here and for full details you can check the answer.


The 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.

OAuth 2.0 by Example – Public clients with resource owner flow

Scenario at hand

  1. You have a public client for which no client secret was issued – it’s only fair as they can’t really keep a secret
  2. You want to use the resource owner password credentials grant from that client


The resource owner flow states that the client should authenticate when doing a request, but the client in question as no means to authenticate

How to do it

(based on an original answer on StackOverflow)

It’s true that a client_id stored on public clients can’t be protected, but this identifier is not a secret, the specification mentions that explicitly.

The client identifier is not a secret; it is exposed to the resource owner and MUST NOT be used alone for client authentication. (source: RFC 6749, section 2.2)

This means that it’s acceptable to have the client_id available on your public client, but there’s still the issue of client authentication.

The specification for this grant mentions that client authentication must be required for confidential clients or any client that was issued credentials. This application does not fall into either one of these cases so the client authentication requirement is not applicable.

Okay, so the client does not require (and can’t actually perform) client authentication, but what if we want to know server-side to which client we are responding to so that we apply conditional logic?

First thing is to pass the client_id to the server. If the client was confidential this would be passed in the Authorization header along with its secret, but that’s not the case. However, the specification allows the client_secret to be omitted so lets still use that HTTP header to pass the client identifier.

client_secret: REQUIRED. The client secret. The client MAY omit the parameter if the client secret is an empty string. (source: RFC 6749, section 2.3.1)

We have the client_id on the server side, but we can’t trust it, not according to the specification, because as we’ve already mentioned we can’t use this identifier alone for client authentication and even if we tried some clever (aka very easy to get it wrong without knowing it) authentication mechanism, there’s also:

The authorization server MAY establish a client authentication method with public clients. However, the authorization server MUST NOT rely on public client authentication for the purpose of identifying the client. (source: RFC 6749, section 2.3)

It seems we’re out of luck, but there’s still:

When client authentication is not possible, the authorization server SHOULD employ other means to validate the client’s identity — for example, by requiring the registration of the client redirection URI or enlisting the resource owner to confirm identity. (source: RFC 6749, section 10.1)

We did have the resource owner participation during the request so we can argue that the client is a well-known application (the resource owner at least trusted it enough to provide their password) and rely on the provided client_id to apply some conditional logic to the request. Okay, that settles it!

However, be sure to still treat this as a non-authenticated client so any conditional logic should not do anything that can be considered risky within the context of public clients and of course, use this grant only if absolutely necessary.