Hacker News new | ask | show | jobs
by jcrites 1079 days ago
> It is now recommended to have a refresh token with an expiration date, that can be long, and have that refresh token be single use. When it is sent to the authorization server to get a new access token, the authorization server will revoke the old refresh token and issue new refresh and access tokens.

I've been wondering briefly about this specific flow. It seems prone to a problem: if the refresh request gets sent successfully (and then invalidated), but the reply is not received for any reason, then the authorization chain is broken. There's no way to get a new token. Is there a good 'standard' way to handle this problem?

On brief reflection, while writing this comment, it seems like the only solution is to fall back to some other (long-lived) identity (from which the original OAuth token was derived). But that can be an inconvenient fallback.

> The interesting property here is that if the authorization server sees the same refresh token twice, it means that the token was stolen: either the thief or the legitimate client already used the refresh token, and the other one is now requesting an access token too. In that case, the authorization server must revoke all current refresh and access tokens for this client.

That seems like an invalid conclusion to draw though. Multiple requests could simply be caused by failures (including race conditions). Should the refresh token really be single-use?

One alternative that I could imagine is that the refresh token can be used multiple times, but whenever it's used, then only the most-recently created tokens remain valid -- with all prior tokens (that were created from it) being invalidated. This would enable token refresh to survive failures, while also making any tampering evident (due to the appearance of unexpectedly invalidated tokens).

Edit: Another strategy might be to link together all access tokens into one "session": whenever a refresh token is used to create new tokens, those all count as the same "session" and the session is what's invalidated. (The 'session' would be established during the process issues the first tokens).

3 comments

> On brief reflection, while writing this comment, it seems like the only solution is to fall back to some other (long-lived) identity (from which the original OAuth token was derived).

I have been working on an OAuth service provider for the past few months, ran into this scenario. We came up with a solution of not immediately expiring the refresh token after it's used but set its expiry to X seconds (<30s) in the future and put it in a leeway state. If another call with the same refresh token is received within the X seconds and the refresh token is in the leeway state, a new token pair is created. If the refresh token is used after X seconds, it's no longer valid and a new authorization has to be generated.

Of course this isn't a foolproof solution, it has its own caveats but it's better than the alternative of forcing the user to go through the authorization process again or keeping the refresh token alive forever.

One option could be to require that the access token needs to used within X seconds after it has been issued. During this period:

* Old refresh token can be used. Using it will revoke previous "new" tokens (and possibly generate some warnings, especially if new tokens are used after this)

* Using new access token will revoke old refresh token and access tokens, possibly requiring access on dedicated path (something like lookup, essentially confirming that you received it)

This probably should be rate limited to avoid malicious/buggy client filling the revoke list with junk. And it may also make sense to decrease the lifetime of the old refresh token (e.g. if it was originally going to expire in 24h make it expire in 30 minutes) or set maximum of times this swap can happen.

Of course this would still cause issues if e.g. database server where these were stored failed and client had to restore from backups.

I do think it's a practical control if you are responsible for both the server AND all the client(s) code, i.e, it's feasible for you to micro-manage the fine detail of the auth dance.

However if you are providing an oauth / oidc API endpoint to be consumed by arbitrary developers I wouldn't advise this.

The way many clients work is that refresh token stuff happens in the background as needed, "piggybacking" off the thread using the access token. Depending on how everything is set up, parallel requests can be generated.

Providing support for oidc / oauth token flows is already extremely difficult because customers will usually be using an ecosystem-specific library and usually don't understand the spec, let alone whatever stricter "best practices" you might be enforcing.