Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Public Client : Refresh Token Revocation on Token Reuse #1619

Closed
awoodobvio opened this issue Mar 4, 2022 · 13 comments
Closed

Public Client : Refresh Token Revocation on Token Reuse #1619

awoodobvio opened this issue Mar 4, 2022 · 13 comments
Assignees
Milestone

Comments

@awoodobvio
Copy link

awoodobvio commented Mar 4, 2022

Refresh Token Revocation on Token Reuse

Problem

When using refresh tokens in a public client (mobile, spa) client authentication cannot be used to protect the /token endpoint. This means that the token can be "stolen" and used by a malicious actor. When this occurs, the application can be compromised by that attacker.

PKCE protects the /authorize endpoint, but that can't be used to exchange the refresh token. Since we are a public client, the refresh token is essentially a bearer token with no additional security.

This means that if a malicious actor gains access to the refresh token, they have an entry point to gaining access to the system with no additional layers of protection.

The suggested mitigation strategy is to use "one time" refresh tokens with short durations and to detect the attempt to use an already used token. When this is detected, all refresh tokens for that "chain" of refresh tokens should be revoked since the assumption is that the refresh token is compromised. This would close this particular attack. This combines the "refresh token rotation" suggestion with a detection of re-use of the token.

Today, Fusion Auth supports the rotation portion but not the revocation portion.

This allows the following attack:

  1. Attacker/Malicious code gains access to refresh token 1
  2. Attacker immediately uses refresh token 1 to gain new access token, refresh token 2
  3. Real code attempts to get a new access token with refresh token 1 and is refused due to one-time refresh token
  4. Attacker continues to use refresh token 2 and new refresh tokens to access application

Solution

When a one time token that was previously used to refresh the access token is used again, the entire chain of refresh tokens that have been minted from that re-used token must be invalidated so that the attacker cannot refresh their access token again.

Alternatives/workarounds

In the post in the forum, a discussion was raised about using web hooks to do this. Unfortunately, this won't work since the detection of the invalid JWT doesn't raise a web hook call. We'd need a "refresh token rejected" call to do this properly. Otherwise, we'd have to keep refresh tokens as re-usable and that opens other security issues.

Additional context

Original Post in Forum
Specification about Refresh Token Abuse Detection when Client Secret is not used
Detail of Attack/Mitigation (Auth0)
Detail of Attack/Mitigation (Ping Identity)
Detail of Attack/Mitigation (Log Rocket)
Add any other context or screenshots about the feature request here.

Related

Community guidelines

All issues filed in this repository must abide by the FusionAuth community guidelines.

How to vote

Please give us a thumbs up or thumbs down as a reaction to help us prioritize this feature. Feel free to comment if you have a particular need or comment on how this feature should work.

@mooreds mooreds added enhancement New feature or request feature labels Mar 4, 2022
@glen-84
Copy link

glen-84 commented Apr 11, 2022

@mooreds This seems like quite critical functionality. Should it not at least have the security label?

@robotdan
Copy link
Member

robotdan commented Jun 6, 2022

I would always strongly suggest you do not manage refresh tokens on the client side.

There are better ways to manage tokens.
https://fusionauth.io/learn/expert-advice/authentication/login-authentication-workflows

Doing everything on the client may be more convenient, but it is less secure and not a recommended way to utilize refresh tokens.

@robotdan
Copy link
Member

It likely would not be practical to preserve all refresh tokens for all time when using one time use refresh tokens.

A couple of ideas:

  1. When using one time use tokens, we increase the entropy (length) such that a portion of the token can be fixed which could then be used to represent all refresh tokens in the sequence. This way we can still easily identify the token you are trying to use and maintain a proper index, so if the prefix is correct, but the suffix is incorrect we know that you are trying to use an "old token" and we can revoke the current value.
  2. We add configuration to keep track of n tokens in the chain. This seems much less valuable because we could then only detect an attempt of re-use as long as it is within n attempts.

Perhaps there are other strategies as well w/out having to persist every refresh token even after it is no longer in use.

@awoodobvio
Copy link
Author

I would always strongly suggest you do not manage refresh tokens on the client side.

There are better ways to manage tokens. https://fusionauth.io/learn/expert-advice/authentication/login-authentication-workflows

Doing everything on the client may be more convenient, but it is less secure and not a recommended way to utilize refresh tokens.

I'm not sure I understand since the authorization code flow returns a id token, access token and a refresh token. Is your suggestion that only the backend of the client application manages the relationship to FA? I'm not sure how that works with the authorization flow since all of that happens with redirects on the client side.

Traditionally the refresh token is kept on the client side to refresh the access token and keep the session alive.

Even your site mentions this:

IMPLEMENTATION NOTE: Some experts recommend that native applications (including mobile apps) use OAuth’s
authorization code grant. This method works fine with many IdPs, including FusionAuth, but is not listed in this section
because it is covered in the SPA and Webapp sections above. The only difference is that at the end of the OAuth workflow,
the native application pulls the JWT and refresh tokens from the web-view.

@awoodobvio
Copy link
Author

awoodobvio commented Jul 22, 2022

It likely would not be practical to preserve all refresh tokens for all time when using one time use refresh tokens.

A couple of ideas:

  1. When using one time use tokens, we increase the entropy (length) such that a portion of the token can be fixed which could then be used to represent all refresh tokens in the sequence. This way we can still easily identify the token you are trying to use and maintain a proper index, so if the prefix is correct, but the suffix is incorrect we know that you are trying to use an "old token" and we can revoke the current value.
  2. We add configuration to keep track of n tokens in the chain. This seems much less valuable because we could then only detect an attempt of re-use as long as it is within n attempts.

Perhaps there are other strategies as well w/out having to persist every refresh token even after it is no longer in use.

You could also add a well known claim into the token to indicate what the originating token was (first access token). Then all refreshes would would copy that forward and you only have to lookup the 1 token.

Of course this only works if the refresh token(s) are JWTs or something where that can be encoded.

@robotdan
Copy link
Member

robotdan commented Jul 22, 2022

You could also add a well known claim into the token to indicate what the originating token was (first access token). Then all refreshes would would copy that forward and you only have to lookup the 1 token.

The only thing that is guaranteed to be provided is the refresh token which is opaque. Claims in the access_token or id_token are not helpful in this workflow. We could require them - but that would be outside of the Refresh Grant spec.

We could make the RT a JWT, but that would increase the size. The RT is currently completely opaque and is just a random string essentially. This is useful because it has no meaning to anyone and does not identify the user, application, etc.

@awoodobvio
Copy link
Author

awoodobvio commented Jul 26, 2022

Understood - by claim I meant a claim on your side with the opaque refresh token. How that is done / encoded into the token is up to you. Didn't mean it had to be a JWT - just that the refresh token would require some encoding mechanism to indicate the root parent. So your mechanism of using a portion of the token as a fixed component would work.

@ansonallard
Copy link

I would always strongly suggest you do not manage refresh tokens on the client side.

There are better ways to manage tokens. https://fusionauth.io/learn/expert-advice/authentication/login-authentication-workflows

Doing everything on the client may be more convenient, but it is less secure and not a recommended way to utilize refresh tokens.

I'm not sure if I'm missing something, but in the link you provided, all of the SPA related articles describe storing the refresh tokens in cookies, which are stored on the client.

Does FusionAuth plan to address the attack vector/vulnerability, when storing and managing refresh tokens on the client?

@robotdan
Copy link
Member

I don't believe we have any examples or recommendations to store refresh tokens client side unless in a secure HTTP only cookie. If you do find one, please let us know and we will correct it.

If the refresh token is stored in an http only cookie, you cannot manage it client side.

Does FusionAuth plan to address the attack vector/vulnerability, when storing and managing refresh tokens on the client?

This is something we'd like to solve. But I don't think this has anything to do with managing refresh tokens on the client. This should be avoided at all costs.

Separately, if using a one time use refresh token, and a previously valid value in the sequence is attempted to be used, ideally we would add a policy to optionally invalidate the currently valid value as well.

@awoodobvio
Copy link
Author

@robotdan I'm struggling to find examples of what you are referring to. Everything so far seems to indicate that the authorization code flow exchanges the code for a (id, access refresh) token set and that seems to be done in the SPA / Web Browser. Numerous articles are written about storing these in local storage, browser memory, etc.

Can you please point me to how to do this better since you seem to indicate this is an anti-pattern. I want to make sure we are implementing security the right way.

Notably, I'd want this for:

Web Based SPA
Mobile Application

Also, we'd like to be able to access the system using a fingerprint/etc. on the mobile app. Today we are doing that with the refresh token being stored in the keychain and unlocking that with the fingerprint. If that isn't the right way to do it, please advise as well.

None of this is relevant to this ticket, however. This ticket is about duplicate use of the refresh token and invaliding the entire refresh chain upon detecting this situation.

@tibohei
Copy link

tibohei commented Jul 14, 2023

A good illustration of the problem in the article An in-depth look at refresh tokens in the browser.

Please implement!

@Zeppelin456
Copy link

I think this is essential and has been implemented by Auth0 - essentially the scenario is that a one time use token is received by the client on refresh, which is not used by the client until the auth token expires. In between this time the refresh token has been used by an attacker to generate a new access code, and potentially a new refresh token. The client's auth token then expires and client uses the original refresh token, which will be rejected as invalid and the client will need to log in again. When the refresh token is used for the second time, it should trigger a revoke on all other refresh tokens for that application / tenant / user, otherwise they can continue to be used to regenerate further single use refresh tokens. A window in seconds for how long a previous token can remain valid after new refresh token generated would be useful so race conditions can be avoided.

@andrewpai andrewpai added this to the 1.55.0 milestone Dec 20, 2024
@robotdan robotdan moved this to Code complete in FusionAuth Issues Dec 20, 2024
@robotdan robotdan self-assigned this Dec 20, 2024
@robotdan
Copy link
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Delivered
Development

No branches or pull requests

8 participants