Hi. How should I persist tokens in an SPA? As mentioned here, it’s not a good idea to persist the tokens int he local storage, wich makes sense to me. The article is saying If your single-page app has a backend server at all, then tokens should be handled server-side using the Authorization Code Flow, Authorization Code Flow with Proof Key for Code Exchange (PKCE), or Hybrid Flow.. I don’t understand how exactly I should leverage my backend to handle the tokens. I can think of ideas like keeping the access token persisted in the backend and referencing to the browser that has access to the tokens with a session. But this sounds rather odd (I would also need to add CSRF attack prevention when using sessions). What is considered a good implementation to persist the tokens?
P.s. I checked the links in the article but I don’t understand how exactly they can help me to solve this problem.
Here’s the general advice that we give our development teams if they are developing SPAs which target back-end services secured with Auth0. This advice might not match exactly your use cases so YMMV (and apologies if you already know some of this - it’s a cut-and-paste!):
Summary Make an authorization call to Auth0 from your SPA, requesting an access token for the API audience and scopes you want to use. You can also request an ID token if you need to identify the user in the SPA (display their name, email address etc.). Keep the access token in memory in your SPA and use it when you make calls to your API.
Use Authorization Code Grant with PKCE from the SPA. This is actually a little difficult with Auth0 at the moment because their production Javascript SDK doesn’t support it. They have released a beta SDK for SPAs which does which we’re currently evaluating. If you must go live in the meantime, use Implicit Grant but plan to move to Authorization Code Grant with PKCE as soon as possible.
Do not use refresh tokens from SPAs. Refresh tokens (which you get if you specify the offline_access scope) cannot be securely stored on browsers and, if revealed, can be used without any knowledge of or further interaction with the user to access your APIs.
Hold access tokens in memory only. Do not use local storage.
Do not use ID tokens as API credentials. ID tokens tell you who the user is. They don’t represent permission to access a particular API and set of scopes - that’s what access tokens are for. You can ask for both tokens with a single request.
Make sure that your API validates the access token for every single request. Most server-side middleware will do this automatically but check that it’s configured properly and don’t be tempted to take shortcuts.
Thank you @glenn.davies for sharing this with me (us). I am taking care of the last four points of the list your shared (but it’s good to have validation that other companies are handling “things” like we do too).
I am not entirely sure what Authorization Code Grant with PKCE is. So I googled and found this article but I am not entirely sure how it relates to SPA’s since it’s saying “Apis and Mobile Apps”. Would you mind to elaborate how you are using Authorization Code Grant with PKCE with SPA’s @glenn.davies? Right now we are using the auth0 lock wich is using the implicit flow described here if I am not mistaken.
@glenn.davies Regarding Hold access tokens in memory only. Do not use local storage., how do you handle page reloads? The token would be gone and the user would have to login again. The only possible way I can think of to solve this, is to persist the token in a backend and then let the user get it from there.
The Auth0 documentation I linked in the question is saying If your single-page app has a backend server at all, then tokens should be handled server-side using the Authorization Code Flow, Authorization Code Flow with Proof Key for Code Exchange (PKCE), or Hybrid Flow.. What does tokens should be handled server-side mean? Should we store them server side? If so this opens other attack vectors such as CSRF attacks since we somehow need to put a session in the browser that connects the browser to the token. It’s not that I don’t know how to do CSRF attack prevention. I am just not sure if I am understanding the auth0 documentation correct and I don’t want to put our users safety on the line. Maybe there is something I am miss understanding. So I am happy to hear how you are handling this @glenn.davies.
Thank you for your help.
P.s. @dan.woda maybe other devs are having the same questions (or they just use local storage :D). It would be good to get some more guidance from auth0 regarding backend server at all, then tokens should be handled.
There’s a good (if rather long!) description of ACG with PKCE in this article from the Auth0 blog. However, in summary, it’s an alternative to implicit grant for OAuth2 public clients. It applies to any client which doesn’t have a secret key - which includes SPAs but also, typically, device native mobile applications.
In implicit grant, the ID and access tokens are delivered directly from the authorization server in the redirect back to the client’s redirect URI. This isn’t ideal from a number of perspectives (read Vittorio’s article for the details).
ACG with PKCE solves the problem by using some basic crypto and what is essentially a one-time password issued by the client called the verifier. The client sends a challenge, derived from the verifier, with the initial authorization request.
After user authentication / consent, the client then receives a single-use authorization code from the authorization server which it then presents back to the authorization server along with the verifier from which the challenge was derived. This second request is made direct to the authorization server. In an SPA, this is done with Javascript and, thus, CORS must be configured correctly on the authorization server. This can be done in Auth0 on the client configuration page.
Regarding page reloads, yes this will cause the access token to be lost. However, when your SPA makes another authorization request, if the Auth0 signon session is still open (and it probably will be) the authorization request will be silent. The user won’t be required to sign in again (unless you specify prompt=login).
I’m not sure I understand the statement in the Auth0 documentation about handling tokens server side. Maybe someone from Auth0 can comment on this.
@glenn.davies Thank you very much for your response about ACG with PKCE.
After user authentication / consent, the client then receives a single-use authorization code from the authorization server which it then presents back to the authorization server along with the verifier from which the challenge was derived. This second request is made direct to the authorization server. In an SPA, this is done with Javascript and, thus, CORS must be configured correctly on the authorization server. This can be done in Auth0 on the client configuration page.
This solved my initial question / provided a solution. Since we can get new access tokens from the logged in session, there is no need to store the access token.
@dan.woda still, it would be interesting to know what auth0 means by “handling tokens server side”
This would look like a more traditional web app in terms of how the token is stored. The SPA will keep a session with the server using cookies, but the token will be held server side, making it more secure in that sense.
So I actually read the article but I am not sure if I am getting it right. We are using auth0 js and I changed the response type to “code”. I am not getting back a “code” and a “state” parameter. I guess the state parameter is used for CSRF protection. Does this mean I am using PKCE? I guess I am not since the requests and responses don’t match with the requests from the article. I am e.g. missing the code_challenge_method parameter in my request. So what exactly am I using right now? just ACG? What exactly is the difference in terms of security if I am just using ACG without PKCE? As far as I understood PKCE is providing CSRF protection (or did I get it wrong)?
@glenn.davies does the use of ACG with PKCE forces me redirect the user to the auth0 domain and have them enter their credentials there? Or can I still have e.g. the javascript lock on my SPA page without the redirect to auth0 for authentication.