Skip to content

guidance on issues of using personal oauth flows for a service to get long term access and then refresh fails and then service does not have access #213

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

Open
dickhardt opened this issue May 14, 2025 · 12 comments
Assignees

Comments

@dickhardt
Copy link
Collaborator

feedback from AuthCon

@dickhardt dickhardt self-assigned this May 14, 2025
@voidmain
Copy link

A good example is something like this (application is synonymous with client):

  1. Application uses Personal (i.e. human clicking) OAuth flow to get an access token and refresh token
  2. Application stores these tokens in the database
  3. Application uses access token to make API calls
  4. At some point in the future, application either checks the token (JWT) to see if it is expired or makes an API call and gets a error response indicating it is expired
  5. Application then requests a new access token via the Refresh Token Grant
  6. Server generates new access token and new refresh token, since it rotates refresh tokens during the Refresh Token Grant
  7. Network/server crashes and application never sees the response
  8. Application can no longer call APIs and cannot refresh its tokens either

In this case, the refresh token the application has in the database is invalid as is the access token, but the application has no way to recover since the new refresh token is lost. This requires that the user logs back into the application and completes the Personal OAuth flow again in order to generate 2 new tokens (access and refresh). Until a user logs in and actively completes this process, the application is essentially inoperable with respect to the service it is using the tokens to interact with.

There is likely a discussion around storing N refresh tokens and deleting the oldest ones once newer ones have been used. However, this seems to be just as risky as never rotating refresh tokens, which basically solve this problem entirely.

@njwatson32
Copy link

An option that would likely address the majority of practical problems is to allow a small grace period before the AS invalidates the old RT, e.g. 60 seconds. This allows the client to retry transient errors with the old RT. I don't think this compromises the security of RT rotation too much, as an attacker with a stolen RT would need to ensure nearly synchronized refreshes with the real client in order to keep the token active. Real clients can guard against this by introducing jitter in their RT refresh cadence.

@dickhardt
Copy link
Collaborator Author

Another option would be to use DPoP in the initial token request, and in refreshes, which would provide assurance it is the same client on refresh which could remove the desire of rotating refresh tokens.

@voidmain would managing DPoP be preferable to the rotating refresh token issues?

@voidmain
Copy link

@njwatson32 - a grace period would certainly work. However, that type of solution should be configurable and will require some testing. It's similar in nature to a thread-safety issue where timing comes in to play and it's not always deterministic when the second request will happen or if it will fail.

For example, the IDP might be in an AWS Region that experiences an outage and the first request occurred right as the outage started. That means the second request might be 30 minutes later.

@dickhardt - I don't think DPoP helps much because this is a race condition not a proof problem. Can you expand on your DPoP idea so we can discuss?

@dickhardt
Copy link
Collaborator Author

@voidmain DPoP could allow an AS to not rotate the refresh token, removing any race condition.

@voidmain
Copy link

@dickhardt - Ah. That makes sense. Though leaking keys and refresh tokens is still an issue. But it would at least reduce some threat vectors.

However, I would only make that one of the possible options. This allows clients that don't want to or aren't capable of using DPoP to still handle refresh token issues with a configurable grace period.

@njwatson32
Copy link

@voidmain yeah, the grace period likely won't address that AWS regional example you gave, but I'd expect that to be a small outlier compared to the much more common transient network errors. Configurable grace period also seems reasonable.

@dickhardt
Copy link
Collaborator Author

@voidmain why would DPoP not be possible for a client?

A configurable grace period would be new functionality, which is out of scope for OAuth 2.1

DPoP is an existing spec and using it is a best practice.

@voidmain
Copy link

@dickhardt - ah the challenge of getting customers to use features. WebAuthn is still at less than 10% adoption still. And some customers still don't use PCKE.

As a vendor, we rarely force things. For FusionAuth, we would make this optional, even if the spec requires it. Otherwise, customers will go find a solution where it's optional.

We even break spec and allow wildcards on redirect_uri, because.... customers demand that feature.

@dickhardt
Copy link
Collaborator Author

@voidmain you are free to implement whatever you want of course! ... and I'm a big fan of giving customers what they want.

As an industry we have a responsibility to improve the security of our customers, and to design things that work. The refresh is a problematic space. We can provide guidance wrt a grace period as a best practice.

We can also describe how DPoP can be a substitute for rotating refresh tokens.

WebAuthn is still at less than 10% adoption still.

I'm not surprised ... but that is a different spec! :)

@voidmain
Copy link

@dickhardt - agreed on all of that. Just want to give choices and options and only use the word "MUST" when absolutely necessary. 😁

@dickhardt
Copy link
Collaborator Author

Even with a MUST -- your implementation can do whatever it wants!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants