Want to learn more about OAuth 2.0 and OpenID Connect?
Save yourself days of digging through dozens of specs with this online course
More informationAn in-depth look at refresh tokens in the browser
Single Page Applications can use refresh tokens in the browser. Yes, you read that right. This new development is awesome, because it makes access token renewal much more elegant. However, refresh tokens in the browser require additional security measures, such as refresh token rotation. We discuss the pros and cons of refresh token rotation, along with the potential dangers. In the end, you will find five strategies you can use to secure your tokens in your web frontends better.
30 April 2020 OAuth 2.0 & OpenID Connect OAuth 2.0, Refresh tokens, XSS
Refresh tokens for everyone
The original purpose of refresh tokens is to obtain long-term access to an API on behalf of the user. In such long-term scenarios, the user is not always present. Hence, the refresh token allows an application to autonomously obtain a new access token from the security token service, without user intervention.
Traditionally, refresh tokens were intended to be used by server-side clients, such as a backend web application. Such an application runs on the server, which we consider a somewhat trusted and secure environment. Additionally, server-side web applications have the ability to encrypt the tokens before storing them, making it significantly harder to steal and abuse refresh tokens.
As the popularity of mobile devices increased, so did the need to use refresh tokens in native applications. After all, no-one wants to continuously authenticate to a mobile application. That’s why a separate specification allows native applications to use an Authorization Code flow with PKCE to obtain a refresh tokens. Again, the application is responsible for storing these tokens securely. In practice, this comes down to encrypting the tokens before storing them on the device.
For web applications, the story is a bit more complicated. Web applications rely on the Implicit flow, which is not allowed to issue refresh tokens. However, with the deprecation of the Implicit flow in favor of the Authorization Code flow for web applications, all that changed. Web applications can now use the Authorization Code flow with PKCE, allowing them to obtain refresh tokens.
But should they?
WTF, refresh tokens in the browser?
Using refresh tokens in the browser is weird. Most users do not expect a web application to have long-term access. So why does a web application need a refresh token?
The main reason to use refresh tokens in web applications is to reduce the lifetime of an access token. When a web application obtains an access token with a lifetime of five to 10 minutes, that token will likely expire while the user is using the application. To obtain a new token, web applications needed to rely on clunky constructs, such as an iframe-based silent authentication flow. To make matters even more complicated, some browsers block the use of cookies during silent authentication, which breaks the flow.
In that context, refresh tokens for web applications make much sense. They allow the web application to obtain new access tokens using straightforward API calls without running into cookie blocking issues.
However, web applications are also notoriously insecure. One snippet of malicious JavaScript code, coming in through an XSS vulnerability or a compromised remote code file, can quickly result in the theft of a refresh token. This nightmare scenario gives the attacker long-term access to an API on behalf of the user — something you want to avoid at all costs.
Protecting your refresh tokens
Browser-based applications cannot just start using refresh tokens and hope for the best. Since web applications are more straightforward to compromise than native applications, refresh tokens require additional protection. Concretely, refresh tokens exposed to the browser should be protected with Refresh Token Rotation (RTR).
In a nutshell, RTR makes refresh tokens only valid for one-time use. Each time a refresh token is used, the security token service issues a new access token and a new refresh token. With the new access token, the client can make API calls on behalf of the user, and with the refresh token, it can run a new Refresh Token flow when needed.
Note that merely issuing new refresh tokens does not provide additional protection. The second aspect of RTR is, therefore, crucial to make refresh tokens more secure. When the security token service detects that a refresh token is used more than once, it assumes that something is wrong. As a result, the refresh token should be immediately revoked. Additionally, every descendant refresh token should also be revoked, to ensure that this chain of tokens is no longer valid.
The image below shows a timeline with legitimate use in blue/grey, and the malicious behavior in red. In this scenario, the attacker steals a refresh token and attempts to use it after the application has already used it. The STS will detect the double usage and revoke the token, along with all its descendants.
Revoking all descendants is crucial to avoid abuse in case the attacker uses the stolen refresh token before the application does. You can see that scenario in the timeline below.
This explanation of RTR is rather brief, but this excellent blog post by Auth0 offers a more detailed explanation.
RTR makes it possible to detect and prevent the use of a stolen refresh token. It is a robust security pattern that can even be used for non-browser applications. However, RTR does not magically make client applications and refresh tokens more secure.
Learn more about OAuth 2.0 and OIDC in SPAs
This online course will answer your questions on security best practices
More informationWhen a JWT access tokens gets away
Refresh tokens are often seen as this big juicy target, but access tokens are the real treasure. With a legitimate (bearer) access token, the attacker can make API calls in the name of the user for as long as the token remains valid. This should make you somewhat uncomfortable, especially in a context where the application relies on RTR for security.
The value of RTR is that a security token service can detect the re-use of a refresh token. This implies that the attacker was able to steal a refresh token from the application in the first place. If the refresh token can be stolen, then so can the access token. With such an access token, the attacker can start making API calls.
To make matters even more complicated, access tokens are often self-contained JWT tokens. Such tokens contain all the information needed for the API to make security decisions. While these tokens offer more flexibility than reference tokens, they are a lot harder to control. Contrary to reference tokens, the security token service is not involved in the validation of a self-contained access token when it is used. As a consequence, self-contained access tokens cannot easily be revoked. This implies that a stolen self-contained access token can be abused until it expires, even when the refresh tokens have been revoked due to re-use.
To summarize, a successful token theft attack gives the attacker immediate access to an API on behalf of the user with the access token. One mitigation strategy is to use reference tokens, which can be actively revoked along with a re-used refresh token. For self-contained tokens, it is recommended to keep the lifetime of these tokens as short as possible to limit abuse. Lifetimes of five minutes for tokens issued to web-based clients are no exception.
Stealthy online token theft
The root of the problem here is the attacker’s ability to steal tokens from an application. When that is possible, even short token lifetimes can still lead to major abuse scenarios.
Let’s look at another timeline example. The top part of the timeline below shows a web-based client using a refresh token to obtain a new access token. The lifetime of an access token is limited to five minutes. The application is typically used for longer than 5 minutes, so it also receives a refresh token. RTR is applied whenever the client runs a refresh flow.
On the bottom part of the timeline, you can see the attacker’s behavior. In this scenario, the attacker has abused an XSS vulnerability to inject malicious code into the application. The attacker is aware that the security token service applies RTR, so the attacker makes sure not to use the application’s refresh token. Instead, the attacker just waits for the application to refresh its own token. When that happens, the attacker can intercept that token in the JS execution environment and ship it off to an external location. The attacker keeps doing this as long as the application keeps running in the user’s browser.
This scenario describes an online token theft attack, which only works as long as the application is running in the user’s browser. During the attack window, the attacker can steal and abuse access tokens. The security token service has no way to detect this behavior since the refresh token is never re-used and the RTR protections are never triggered.
Nothing new under the sun
In the next section, I will discuss a couple of strategies to reduce the impact of these attacks. Before I do that, I want to make something clear. These attacks are not new attacks.
Even without the use of refresh tokens in the browser, an XSS vulnerability in the application will result in token theft. An attacker could inject malicious code to run a silent authentication flow, which re-uses the user’s session to issue new tokens. Similarly, the attacker could just wait for the application to run such a flow, and then steal the access tokens from the application.
The point I want to make here is that RTR offers the same level of protection than using access tokens with silent renew. It does not make an application less secure, but also not more secure. With that in mind, refresh tokens are a much more beautiful abstraction than the use of iframes, which makes RTR so great.
Locking down OAuth 2.0 in web applications
The previous section already made clear that token theft is a well-known threat in frontend applications using OAuth 2.0. So let’s take a look at five defensive strategies than can help prevent token theft or reduce its impact.
Strategy #1: prevent XSS vulnerabilities: When an application is vulnerable to XSS, the attacker will be able to add malicious code to the application’s execution context. Preventing harm when that happens is virtually impossible. That’s why your first focus should be to prevent XSS. That requires learning about the security features of your framework and doing a thorough code analysis. These cheat sheets for securing Angular applications and React applications can help you with that.
Strategy #2: Keep access token lifetimes short: Access tokens issued to frontend web applications will be more exposed, whether you like it or not. Since a frontend can use a refresh token, there is little harm in keeping short lifetimes for access tokens. It will require a few more Refresh flows, but also reduces the damage when an access token is stolen. Note that this does not give you more security, just a bit more control.
Strategy #3: Make the refresh token dependent on the user’s session: RTR is not a catch-all security measure. If an attacker manages to obtain the last refresh token before the app closes, they might be able to keep rotating the stolen refresh token. To avoid long-term abuse of a stolen refresh token, the security token service can link the lifetime of that refresh token to the lifetime of the user’s session with the security token service. Doing so would invalidate the refresh token when the session expires.
Strategy #4: Use reference tokens: Instead of using self-contained JWT tokens for access tokens, you can use reference tokens. These are controlled by the security token service and can more easily be revoked. The drawback of reference tokens is that APIs cannot consume them independently, but need to contact the security token service to translate them. This strategy is recommended for security-sensitive scenarios.
Strategy #5: Use a BFF: To be honest, browser environments are ridiculously hard to secure. By using a Backend For Frontend (BFF) pattern, your SPA uses a dedicated lightweight API. Such a BFF acts as an OAuth 2.0 client, handling all tokens. The SPA maintains a session with the BFF, which forwards API calls to the designated APIs. While forwarding the call, the BFF attaches the necessary tokens.
To conclude, these strategies are not mutually exclusive, so mix and match to secure your application!
About Dr. Philippe De Ryck
Hi, I'm Philippe, and I help developers protect companies through better web security. Learn more about my security training program, advisory services, or check out my recorded conference talks.
Want to learn more about OAuth 2.0 and OpenID Connect?
Save yourself days of digging through dozens of specs with this online course
More informationDr. Philippe De Ryck
Hi, I'm Philippe, and I help developers protect companies through better web security. As the founder of Pragmatic Web Security, I travel the world to teach practitioners the ins and outs of building secure software.
Talks and workshops
You will often find me speaking and teaching at public and private events around the world. My talks always encourage developers to step up and get security right.
Articles
Security is often about small nuances. In my articles, I dive deeper into various security topics, providing concrete guidelines and advice. My articles also answer questions I often get while speaking or teaching.
Security resources
Getting security right is all about knowledge. I strongly believe in sharing that knowledge to move forward as a community. Among my resources, you can find developer cheat sheets, recorded talks, and extensive slide decks.
Mailing list
Subscribe to the Pragmatic Web Security mailing list to stay up to date on the latest activities and resources.