Why are Refresh Tokens considered insecure for an SPA?
Asked Answered
M

5

36

I was reading the documentation on the Auth0 site regarding Refresh Tokens and SPA, and they state that SPA's should not use Refresh Tokens as they cannot be securely stored in a browser, and instead use Silent Authentication instead to retrieve new Access Tokens.

A Single Page Application (normally implementing Implicit Grant) should not under any circumstances get a Refresh Token. The reason for that is the sensitivity of this piece of information. You can think of it as user credentials, since a Refresh Token allows a user to remain authenticated essentially forever. Therefore you cannot have this information in a browser, it must be stored securely.

I'm confused. From my understanding, the only way to retrieve a new access token would be to submit a new request to the Auth server, along with some form of an Auth0 session cookie to authenticate the user that is logged in. Upon receiving the session cookie the Auth0 server would then be able to issue a new Access Token.

But how is that any different than having a Refresh Token in the browser or in the local storage? What makes the Session Cookie any more secure than a Refresh Token? Why is using a Refresh Token in an SPA a bad thing?

Mvd answered 15/3, 2018 at 2:55 Comment(2)
It says the client should not receive the refresh token, not that they should not be used. They just should not be stored client side.Teahouse
As far as why it is a bad thing... it says right there... "a Refresh Token allows a user to remain authenticated essentially forever." That's the potential downside.Teahouse
S
24

There are a lot of misunderstandings about both cookies and refresh tokens and OAuth2.

First, it is not true that only confidential clients can use a refresh token. The OAuth2 protocol says that confidential clients must authenticate, but does not require confidential clients. Ergo, client authentication is optional on the refresh operation. See RFC 6749, Section 6, Refreshing An Access Token.

Second, you have to understand what the alternatives are:

  1. Forcing the user to enter his or her username and password every 5 minutes (whenever the access token expires)
  2. Long lived access tokens
  3. Authentication via HTTP Cookies

Everybody in the world, who doesn't use refresh tokens, uses option #3. Authentication via cookies is functionally and security-wise 100% equivalent to storing a refresh token. Of course, with both tokens and cookies, there are options for where they are kept:

a. HTTP only, b. secure (require TLS/SSL) and c. session (in memory) vs. persistent (local, domain storage)

The "HTTP only" option applies only to cookies and, thus, may represent the only advantage of using cookies over tokens. I.e. tokens are handled via Javascript, so there's no option to keep them away from scripts. That said, the tokens are available only to Javascript from the domain of the page that stored it (or as allowed by CORS policy). So this issue can be overblown.

Of course, care must be taken to always use TLS/SSL to transmit either authentication cookies or tokens. Honestly, since we know most breaches occur from within the private corporate network, end-to-end TLS is a basic requirement anymore.

Finally, whether cookies or tokens are ever persisted, i.e. stored somewhere that survives closing the browser or even rebooting the device, depends on the trade-off you're making between usability and security - for your application.

For applications that require a higher level of security, just keep everything in memory (i.e. session cookies, tokens in a Javascript variable). But for apps that don't require as much security and really want a session life on order of days or weeks, then you need to store them. Either way, that storage is accessible only to pages and scripts from the original domain and, thus, cookies and tokens are functionally equivalent.

Swetlana answered 3/9, 2019 at 21:42 Comment(4)
> Authentication via cookies is functionally and security-wise 100% equivalent to storing a refresh token. I don't think that's true. The refresh token is stored in the domain where the spa lives. The authentication cookies are stored on the domain where the login page lives. When using #3, you temporary redirect back (usually using an iframe) to the login page to refresh the token using authentication code flow. In my understanding this is what makes it more secure than storing a refresh token.Stone
@JohantHart The details of the token domain depend on the authentication flow. But, yes, point taken. The SPA needs to have access to the token wherever it is stored to pass in REST request headers. You are still trusting the browser to protect the storage, either way. Ime, I have seen a lot of misguided objections to using cookies. As a practical matter, tokens are not subject to XSRF/cookie attacks. So, there are still trade-offs to consider. But cookies+XSRF for authentication and tokens for app access is a solid approach.Swetlana
There is additional levels of security. The use of opaque tokens is becoming more widespread. Take a look at the Token Handler pattern approach for example curity.io/resources/learn/the-token-handler-patternBurford
Fwiw, having a token handling front end service is not a new idea. The obvious con is that now you need CSRF protection on your REST APIs. The benefit is if you inadvertently include malicious code in your own Javascript dependencies, it cannot access the HTTP-only cookie and is, thus, more "zero trust" compliant. With the large dependency trees we all are carrying around, this is a non-trivial consideration. Scanning at build time doesn't help you if the code is already out there ...Swetlana
N
19

This is not true anymore (April 2021), Auth0 site now states a different thing:

Auth0 recommends using refresh token rotation which provides a secure method for using refresh tokens in SPAs while providing end-users with seamless access to resources without the disruption in UX caused by browser privacy technology like ITP.

Auth0’s former guidance was to use the Authorization Code Flow with Proof Key for Code Exchange (PKCE) in conjunction with Silent Authentication in SPAs. This is a more secure solution than the Implicit Flow but not as secure as the Authorization Code Flow with Proof Key for Code Exchange (PKCE) with refresh token rotation.

Please note the importance of enabling rotation in refresh token.

Nixon answered 26/4, 2021 at 13:12 Comment(1)
Using refresh token rotation, what should the lifetime of the access token and refresh token be? Or what is the safest time?Xenon
F
14

The refresh tokens are not used in SPAs, because in order to use it - and to get a new access token from the /token, the SPA needs to have a client secret, which cannot be stored securely in a browser. But since the OAuth 2.0 for Native Apps RFC recommends not requiring a client secret for the /token endpoint (for public clients), the refresh tokens could be used even in SPAs.

To get a refresh token, you need to use the Auth code grant, which passes the code in a redirect URL, which goes to the server hosting the SPA (which can be an extra point of attack). The Implicit grant delivers tokens just to a browser (hash part of the redirect URL doesn't get to the server).

The difference between using a refresh token and an SSO session cookie - the cookie is probably more secure, since it can be marked as HttpOnly, making it inaccessible for attacks using JavaScript code.

Update

With PKCE extension, the Authorization code flow (with a refresh token) became a recommended flow even for browser based applications. For details see the latest version of the OAuth 2.0 for Browser-Based Apps RFC.

Fowler answered 15/3, 2018 at 9:37 Comment(6)
In case of PKCE what parameters are required for grant_type=refresh_token @ján-HalašaBacteriostasis
PKCE defines three parameters - code_challenge, code_challenge_method and code_verifier and it's necessary to know how they work. You cannot use it just for the token endpoint calls without using the first two params for the initial auth call.Kattegat
I've used those params for initial auth call and stored the code_challenge, code_challenge_method. But I couldn't find any docs on what is required for refresh_token call, as I can't send client_secret for SPA, i'm not sure what params are required. Are you suggesting I should send code_verifier along with client_id for renewing token.Bacteriostasis
@Bacteriostasis Did you ever figure out how to refresh in the initial /authorize call ?Biffin
@Biffin what I understood is once you get the refresh token all you have to do is send refresh_token along with clientId for a new set of (access_token and id_token)Bacteriostasis
for initial call just follow the diagram auth0.com/docs/flows/concepts/auth-code-pkceBacteriostasis
O
8

Good question - So there is no really secure way to store any tokens on a Browser (or any other confidential info) - see links such as this. Hence Single Page Apps (SPA) should not store a refresh token - a refresh token is particularly problematic, because it is long lived (long expiration or no expiration), and if stolen then an attacker can continue to refresh access tokens after each individually expires.

It would be better to just retrieve your access token when you need it (for instance to call an API) and either store only in memory (still vulnerable to XSS / CSRF) but better - or use and forget. Then make another checkSession call next time you need an access token.

To your question - the checkSession request does not require sending a Token. It is literally as the name suggests - a "check session" against the Authorization Server to see if a Session exists. If it does, then the Authorization Server response will include a fresh access token. See here for an example usage with SPA

Please feel free to leave me comments beneath this answer if anything requires more clarification etc.

Obregon answered 15/3, 2018 at 3:22 Comment(5)
I still don't understand why a session cookie is any better than a refresh token. In order to get a new access token without a refresh token, I still need to submit a session cookie. Why is that considered any better than a refresh token? The session cookie has the same issue (long lived, and can be used to generate new access tokens) as the refresh token. So why is using the session cookie considered 'secure' whereas the RT is not?Mvd
Eric B. What the author leaves out is that now you are using a session cookie that can be accompanied by an anti-forgery token. Here's more on CSRF prevention with anti-forgery tokens. learn.microsoft.com/en-us/aspnet/web-api/overview/security/…Decuple
What about storing the refresh token within a cookie and properly securing cookie, eg. Http flag set.Herniotomy
Would it make sense if I store the refresh_token encrypted in cookie/server, and only generate a 1 min lifespan access_token by the server through back-end when needed? (To access other resources)Omasum
Session cookies are not stored persistently. So, if you close the browser, you need to log in again (enter username and password, etc.). If you're using cookies for authentication, you should use the OIDC Implicit flow to (silently, after consents) acquire an access token for REST API use. This lets you limit the cookie and CSRF protections to your login server which, presumably, is at a different domain name than your REST service. E.g. login.example.com vs. api.example.com.Swetlana
C
0

We could store the refresh token in a cookie with the httpOnly, secure and signed flags set to true.

NodeJs Example on setting the refresh token in a cookie

const refreshToken = signToken(userId)
const cookieName = "foo-bar-blah"

res.cookie(cookieName , refreshToken , {
      httpOnly: true,
      signed: true,
      secure: true
    })

All we have to do is send the cookie containing the refresh token with a request from the client side without needing to access it at all.

Client-side requests with a library like axios

await axios.get('refresh-endpoint', {
    withCredentials: true
})

withCredentials set to true sends browser cookies with the request.

Anytime the client hits the refresh-endpoint route on the server, the cookie is decrypted with the secret stored on the server and the refresh token is retrieved.

This setup makes it difficult for the refresh token to be accessed by client-side JavaScript. Even if that was possible there's still a problem of retrieving the refresh token from the cookie without the cookie secret on the server.

I think this is fairly secure.

Culmiferous answered 1/8, 2023 at 21:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.