Why Does OAuth v2 Have Both Access and Refresh Tokens?

Viewed 279489

Section 4.2 of the draft OAuth 2.0 protocol indicates that an authorization server can return both an access_token (which is used to authenticate oneself with a resource) as well as a refresh_token, which is used purely to create a new access_token:

https://www.rfc-editor.org/rfc/rfc6749#section-4.2

Why have both? Why not just make the access_token last as long as the refresh_token and not have a refresh_token?

19 Answers

This answer has been put together by the help of two senior devs (John Brayton and David Jennes).

The main reason to use a refresh token is to reduce the attack surface.

Let's suppose there is no refresh key and let’s go through this example:

A building has 80 doors. All doors are opened with the same key. The key changes every 30 minutes. At the end of the 30 minutes I have to give the old key to the keymaker and get a new key.

If I’m the hacker and get your key, then at the end of the 30 minutes, I’ll courier that to the keymaker and get a new key. I’ll be able to continuously open all doors regardless of the key changing.

Question: During the 30 minutes, how many hacking opportunities did I have against the key? I had 80 hacking opportunities, each time you used the key (think of this as making a network request and passing the access token to identify yourself). So that’s 80X attack surface.

Now let’s go through the same example but this time let’s assume there’s a refresh key.

A building has 80 doors. All doors are opened with the same key. The key changes every 30 minutes. To get a new key, I can’t pass the old access token. I must only pass the refresh key.

If I’m the hacker and get your key, I can use it for 30 minutes, but at the end of the 30 minutes sending it to the keymaker has no value. If I do, then the keymaker would just say "This token is expired. You need to refresh the token." To be able to extend my hack I would have to hack the courier to the keymaker. The courier has a distinct key (think of this as a refresh token).

Question: During the 30 minutes, how many hacking opportunities did I have against the refresh key? 80? No. I only had 1 hacking opportunity. During the time the courier communicates with the keymaker. So that’s 1X attack surface. I did have 80 hacking opportunities against the key, but they are no good after 30 minutes.


A server would verify an access token based on credentials and signing of (typically) a JWT.

An access token leaking is bad, but once it expires it is no longer useful to an attacker. A refresh token leaking is far worse, but presumably it is less likely. (I think there is room to question whether the likelihood of a refresh token leaking is much lower than that of an access token leaking, but that’s the idea.)

Point is that the access token is added to every request you make, whereas a refresh token is only used during the refresh flow So less chance of a MITM seeing the token

Frequency helps an attacker. Heartbleed-like potential security flaws in SSL, potential security flaws in the client, and potential security flaws in the server all make leaking possible.

In addition, if the authorization server is separate from the application server processing other client requests then that application server will never see refresh tokens. It will only see access tokens that will not live for much longer.

Compartmentalization is good for security.

Last but not least refresh tokens can get rotated. Meaning 'a new refresh token is returned each time the client makes a request to exchange a refresh token for a new access token.'. As refresh tokens are continually exchanged and invalidated, the threat is reduced. To give you an example: Tokens are usually expired after a TTL usually an hour.

Refresh tokens not always, but often are revoked upon usage and a new one issued. Meaning if you ever have a network failure, when you're retrieving the new refresh token, then the next time you send that refresh token, it's considered revoked and you have to sign in.

For more on rotation see here and here

Summary

  • Reducing Frequency
  • Compartmentalization
  • Rotation (quicker invalidation) and more granular management (expiration time or number of requests made) of tokens.

All help to to mitigate threats

For another take on this see this awesome answer


What refresh token is NOT about?

The ability to update/revoke access level through refresh tokens is a byproduct of choosing to use refresh tokens, otherwise a standalone access token could be revoked or have its access level modified when it expires and users gets a new token

Assume you make the access_token last very long, and don't have refresh_token, so in one day, hacker get this access_token and he can access all protected resources!

But if you have refresh_token, the access_token's live time is short, so the hacker is hard to hack your access_token because it will be invalid after short period of time. Access_token can only be retrieved back by using not only refresh_token but also by client_id and client_secret, which hacker doesn't have.

While refresh token is retained by the Authorization server. Access token are self-contained so resource server can verify it without storing it which saves the effort of retrieval in case of validation. Another point missing in discussion is from rfc6749#page-55

"For example, the authorization server could employ refresh token rotation in which a new refresh token is issued with every access token refresh response.The previous refresh token is invalidated but retained by the authorization server. If a refresh token is compromised and subsequently used by both the attacker and the legitimate client, one of them will present an invalidated refresh token, which will inform the authorization server of the breach."

I think the whole point of using refresh token is that even if attacker somehow manages to get refresh token, client ID and secret combination. With subsequent calls to get new access token from attacker can be tracked in case if every request for refresh result in new access token and refresh token.

Its all about scaling and keeping your resource server stateless.

  • Your server / resource server
    • Server is stateless, meaning does not check any storage to respond very fast. Does this by using a public key to verify the signature of the token.

    • Checks access_token on every single request.

    • By only checking the signature and expiration date of access_token, response is very fast and allows scaling.

    • access_token should have short expiration time (a few minutes), since there is no way to revoke it, if it gets leaked damage is limited.

  • Authentication server / OAuth server
    • Server is not stateless, but its ok because requests are much fewer.
    • Checks refresh_token only when access_token is expired. (every 2 minutes for example)
    • Request rate is much lower than resource server.
    • Stores the refresh token in a DB and can revoke it.
    • refresh_token can have long expiration time (few weeks/months), if it gets leaked there is a way to revoke it.

There is a important note though, the authentication server has much fewer requests so can handle load, however there can be a storage issue since it has to store all refresh_tokens, and if users increase dramatically this could become a problem.

Refresh tokens and Access tokens are mere terminologies.

This little analogy can help solidify the rationale behind using Access Tokens and Refresh Tokens:

Suppose Alice sends a cheque to Bob via post, which can be encashed within 1 hour (hypothetical) from the time of issue, else the bank will not honor it. But Alice has also included a note in the post meant for the bank, asking the bank to accept and encash the cheque in case it gets a bit delayed (within a stipulated range)

When Bob receives this cheque, he will himself discard this cheque, if he sees this tampered (token tampering). If not, he can take it to the bank to encash it. Here, when the bank notices that the time of issue has surpassed the 1-hour time limit, but sees a signed note from Alice asking the bank to encash in case of stipulated delay within an acceptable range.

Upon seeing this note, the bank tries to verify the signed message and checks if Alice still has the right permissions. If yes, the bank encashes the cheque. Bob can now acknowledge this back to Alice.

Although not terribly accurate, this analogy can you help notice the different parts involved in processing the transaction:

  • Alice (Sender - Client)
  • Bob (Receiver - Resource Server)
  • Bank (Authorization Server)
  • Verification Process (Database Access)
  • Cheque (Access Token)
  • Note (Refresh Token)

Mainly, we want to reduce the number of API calls to the Auth Server, and eventually to the Database, in order to optimize scalability. And we need to do this with the right balance between convenience and security.

Note: It's certainly more common to have the Auth server responding to the requests earlier than the resource server in the chain.

From what I understand, refresh tokens are there just for performance and cost savings if you need to be able to revoke access.

Eg 1: Do not implement refresh tokens; implement just long-lived access tokens: You need to be able to revoke access tokens if the user is abusing the service (eg: not paying the subscription) => You would need to check the validity of the access token on every API call that requires an access token and this will be slow because it needs a DB look-up (caching can help, but that's more complexity).

Eg 2: Implement refresh tokens and short-lived access tokens: You need to be able to revoke access tokens if the user is abusing the service (eg: not paying the subscription) => The Short-lived access tokens will expire after a short white (eg. 1hr) and the user will need to get a new access token, so we don't need validation on every API call that requires an access token. You just need to validate the user when generating the access token from the refresh token. For a bad user, you can log out the user if an access token cannot be generated. When the user tries to log back in, the validation will run again and returns an error.

Since the refresh and access tokens are terms loaded with a lot of semantics a terminology shift could help?

  • Revokable Tokens - tokens that must be checked with authorization server
    • could be chained (see RTR - refresh token rotation)
    • can be used to create NonRevokable Tokens, but can also be used directly (when volumes are small and the check doesn't become a burden)
    • can be long lived but that depends on how often the user must be bothered with credentials (username/password) to get a new one
    • can be invalidated on RTR or any other suspect behavior
  • NonRevokable Tokens - tokens that are self contained and do not need to be checked with authorization server
    • are useful for big data, distributed servers/api calls to scale horizontally
    • should be short lived (since are non revokable)

In 2020 it become accepted that refresh token can also exist in the browser (initially was offered for backend systems) - see https://pragmaticwebsecurity.com/articles/oauthoidc/refresh-token-protection-implications. Because of this the focus was switched from the "refreshability" (how would a backend in absence of a user prolong the access to an api) to "revokability".

So, to me it looks safer to read the refresh tokens as Revokable Tokens and access tokens as Non-Revokable Tokens (maybe Fast Expiring Non Revokable Tokens) .

As a side note about good practice in 2021 a system can always start with revokable tokens and move to non-revokable when the pressure on authorization server increases.

There are two important points we need to understand in order to understand the answer to this question.

  1. The first point is, sometimes a user's access token might get stolen without the user knowing anything about it. Since user is unaware of the attack, they will not be able to inform us manually. Then, there will be a huge difference between e.g. 15 minutes and a whole day, with regards to the amount of time(opportunity) we have given the attacker to accomplish its attacks. So this is the reason we need to "refresh" access tokens ourselves every "short period of time" (e.g. every 15 minutes), we don't want to put off doing this for a long time (e.g. a whole day). So what the OP has said in the question is obviously not an option (stretching access token's expiry time as long as refresh token's).

So we have at least these two options left for us:

  • Asking each user to re-enter their credentials every short period of time in order to give them fresh access tokens. But obviously, this is not a popular option as it would be bothering to the users.

  • Using a refresh token. Read the second point below in order to understand how it works (the logic behind it).

  1. The second point to understand is, because we have separated access token from refresh token, now refresh token can be sent in a "different way", so we can send it in a way that it will be inaccessible to attackers' JavaScript (client side code in general), e.g., using the httpOnly tags:

An HttpOnly Cookie is a tag added to a browser cookie that prevents client-side scripts from accessing data. source

Using the HttpOnly flag when generating a cookie helps mitigate the risk of client side script accessing the protected cookie. HttpOnly cookies were first implemented in 2002 by Microsoft Internet Explorer developers for Internet Explorer 6 SP1. source (Thank you IE!)

So while attackers might be able to still steal access tokens (it is highly recommended though to keep them in RAM, rather than in vulnerable places like localstorage), they won't be able to steal refresh tokens. So, if an attacker steals one's access token, they will only have a short period of time for abusing it (15 minutes? A whole lot better than a whole day!), and then as soon as it expires, they will not have a chance to get a fresh one on their own as well.

Related