Is this SPA authentication flow secure?

Because we don’t want our application to redirect to a 3rd party identity provider (e.g. Auth0’s hosted login page), we were thinking about following this flow to authenticate a user in our SPA.

The user inputs his username and password into a form in our SPA. The SPA sends a POST request to the /login route on our backend. The backend then sends a POST /oauth/token with grant_type "password" to Auth0 and forwards the response to the SPA. The SPA then stores the access_token and refresh_token (if applicable) to use for accessing protected routes on our backend. Does this flow make sense? Are there any security holes that I am overlooking?

Disclaimer: A definitive answer to these type of questions is always almost impossible to provide because the overall security for custom implementations that deviate from standardized approaches will depend on every detail and assumption of the implementation and that is usually impossible to translate into a single question.


One (bad) thing does stand out in your suggested approach; a client-side component within a browser-based application should not store refresh tokens. Refresh tokens are usually long-lived credentials and the browser environment is too hostile.

The above limitation would mean that your SPA would not be able to refresh the initial access token after it had expired. If you want to consider your application something more than just a SPA then you could consider storing the refresh token in the server-side and implement your own notion of an authenticated session so that the client-side part (Javascript) would have continuous access even after the first access token expires. However, you would now have to treat your application in a more traditional way and would possibly have additional overhead in terms of implementing your local session in a secure way.

In conclusion, the standard way to implement your scenario and the one with less implementation efforts would be to use the hosted login page and silent authentication for token renewal.

As an additional note, we are considering improving the available scenarios for developers that cannot leverage the hosted login page due to strict requirements beyond their control, however, at this time these are not yet fully available.


UPDATE:

It’s not allowing access with an expired access token; it is allowing access with a still valid local session. That still valid local session could allow you to obtain another access token by using the refresh token on the server, but this would be an implementation detail; the important part is that you would be managing access in your web application through a local session (cookie) and you never exposed refresh token in an environment that would be an XSS away from being compromised.

Both SPA’s and Web applications can keep the user signed in for a long a time; it’s just how they can do so that may be a bit different. Web applications with logic on the backends can use local sessions based on cookies to have complete control; for a SPA this would not be the case, so a SPA could be said that is more constrained in what it can do.

Yeah I saw that it was recommended that the client not store refresh tokens, however, isn’t allowing the client to continually access the service with an expired JWT essentially the same thing? I don’t understand what added security benefit moving the refresh token storage to the server would have. Unless I’m missing something, that approach seems like it would have a large development overhead without a great security benefit.

I don’t believe I understand your definition of a SPA. When using a SPA I’d expect that my session would not expire often, if ever. I realize that definition is application specific but I think it is a common pattern for SPAs (or even web apps in general) to allow clients long-running access to the application (e.g. GitHub, Google Drive, Reddit).

I saw the hosted login page and silent authentication approach as well. However, we want our login page to be hosted on our domain, not Auth0’s.

(Sorry about the two comments - I hit the max character count.)

I updated the answer with additional information about my perspective on things.

Thanks for updating the answer. I still don’t understand the difference between storing a refresh token in a cookie on the client and storing a session cookie on the client. It sounds redundant. What is the reasoning behind this?

A session cookie will be HTTP-only so if you store the refresh token on a cookie accessible to Javascript it’s not the same thing. If you store a refresh token also in a HTTP-only cookie then SPA components won’t be able to access it so in practice you just implemented a local session that is trying to be clever by reusing a refresh token as the session cookie/identifier and avoid using a different opaque identifier specific to the application and some additional server-side storage; personally, I would avoid doing this and would prefer to have an application specific identifier.

Right, that is what I was suggesting. What is the benefit to having an application specific identifier? Implementing a custom session management solution seems like a lot of overhead, and it’s not clear to me what the benefit would be.

Unfortunately in software development not everything is black or white and there are a lot of gray areas. For me, personally, I would prefer to have an application specific identifier as it separates concerns and does not tie the session directly to the refresh token.

Gotcha. Thank you for your help and continued responses!