Specification #
-
Refresh tokens are credentials used to obtain access tokens. [1]
- (1) when current access token expires.
- (2) to obtain additional access tokens with identical or narrower scope.
-
Issuing a refresh token is optional at the discretion of the authorization server. [1:1]
-
Refresh tokens MUST be kept confidential in transit and storage, and shared only among the authorization server and the client to whom the refresh tokens were issued. [2]
-
The authorization server MUST maintain the binding between a refresh token and the client to whom it was issued. [2:1]
-
Refresh tokens MUST only be transmitted using TLS [2:2]
-
The authorization server MUST verify the binding between the refresh token and client identity. [2:3]
-
When client authentication is not possible, the authorization server SHOULD deploy other means to detect refresh token abuse. [2:4]
-
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. [2:5]
-
The authorization server MUST ensure that refresh tokens cannot be generated, modified, or guessed to produce valid refresh tokens by unauthorized parties. [2:6]
- cannot be modified: signing
- cannot be guessed: encryption
Other Specifications #
- OAuth 2.0 Token Revocation [3]
- Refresh Token Expiration
Refresh Access Token #
Why #
- access token usually issued for a limited time.
- In the scenario of an expiring access token, your application has two alternatives:
- Ask the users of your application to re-authenticate each time an access token expires.
- The authorization server automatically issues a new access token once it expires.
How to Refreshing an Access Token ? #
Client Request #
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
Auth Server [6] #
- MUST authenticate the client if client authentication is included
- MUST validate the refresh token.
- IF valid and authorized, the authorization server issues an access token
- The authorization server MAY issue a new refresh token, in which case the client MUST discard the old refresh token and replace it with the new refresh token.
- The authorization server MAY revoke the old refresh token after issuing a new refresh token to the client.
- If a new refresh token is issued, the refresh token scope MUST be identical to that of the refresh token included by the client in the request.
Flow [7] #
+--------+ +---------------+
| |--(A)------- Authorization Grant --------->| |
| | | |
| |<-(B)----------- Access Token -------------| |
| | & Refresh Token | |
| | | |
| | +----------+ | |
| |--(C)---- Access Token ---->| | | |
| | | | | |
| |<-(D)- Protected Resource --| Resource | | Authorization |
| Client | | Server | | Server |
| |--(E)---- Access Token ---->| | | |
| | | | | |
| |<-(F)- Invalid Token Error -| | | |
| | +----------+ | |
| | | |
| |--(G)----------- Refresh Token ----------->| |
| | | |
| |<-(H)----------- Access Token -------------| |
+--------+ & Optional Refresh Token +---------------+
Figure 2: Refreshing an Expired Access Token
Refresh Token Rotation #
- Refresh token rotation is intended to automatically detect and prevent attempts to use the same refresh token in parallel from different apps/devices. This happens if a token gets stolen from the client and is subsequently used by both the attacker and the legitimate client. [8]
- The basic idea is to change the refresh token value with every refresh request in order to detect attempts to obtain access tokens using old refresh tokens. [8:1]
- It's a way to prevent token reuse according to Specification 7
Implementatino Notes #
- In accessing data storage layer, (1) New Refresh Token and (2) Invalidate Refresh Token should be atomic.
- Okta: Refresh token rotation
- Auth0: Refresh Token Rotation
Refresh token reuse detection #
- If a previously used refresh token is used again with the token request, the authorization server automatically detects the attempted reuse of the refresh token. [9]
- As a result, Okta immediately invalidates the most recently issued refresh token and all access tokens issued since the user authenticated. This protects your application from token compromise and replay attacks.[9:1]
- Since the authorization server cannot determine whether the attacker or the legitimate client is trying to access, in case of such an access attempt the valid refresh token and the access authorization associated with it are both revoked. [8:2]
Reuse Scenario [10] #
In these scenarios, the reuse of a refresh token triggers all kinds of alarms with the authorization server. Refresh token reuse likely means that a second party is trying to use a stolen refresh token. In response to this reuse, the authorization server immediately revokes the reused refresh token, along with all descendant tokens. Concretely, all refresh tokens that have ever been derived from the reused refresh token become invalid.
Issue: Client Can Retry v.s. Replay attack #
-
Token reuse detection can sometimes impact the user experience. For example, when users with poor network connections access apps, new tokens issued by Okta might not reach the client app. As a result, the client might want to reuse the refresh token to get new tokens. [11]
-
According to Specification 8, Refresh token reuse detection should be implemented for preventing replay attack.
-
Without enforcing sender-constraint, it’s impossible for the authorization server to know which actor is legitimate or malicious in the event of a replay attack. [12]
- If retry is allowed, it means somehow security is sacrificed in some degree.
-
Related discussion: ory/hydra: Refresh tokens need a grace period to deal with network errors and similar issues #1831
Solution from Okta: Grace period [11:1] #
- Okta offers a grace period when you configure refresh token rotation. After the refresh token is rotated, the previous token remains valid for the configured amount of time to allow clients to get the new token.
- The default number of seconds for the Grace period for token rotation is set to 30 seconds. You can change the value to any number from 0-60 seconds. After the refresh token is rotated, the previous token remains valid for this amount of time to allow clients to get the new token.
Solution from Auth0: Grace period [13] #
- Enter Reuse Interval (in seconds) for the refresh token to account for leeway time between request and response before triggering automatic reuse detection. This interval helps to avoid concurrency issues when exchanging the rotating refresh token multiple times within a given timeframe. During the leeway window the breach detection features don't apply and a new rotating refresh token is issued. Only the previous token can be reused; if the second-to-last one is exchanged, breach detection will be triggered.
Solution from ory/fosite: Grace period [14] #
// https://github.com/ory/fosite/blob/869a37ce486cb0ca921449319e1048004b2f1bd8/handler/oauth2/flow_refresh.go#L144
func (c *RefreshTokenGrantHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) (err error) {
// ...
if err := c.TokenRevocationStorage.RevokeRefreshTokenMaybeGracePeriod(ctx, ts.GetID(), signature); err != nil {
return err
}
// ...
}
- PR on storage implimentation: feat: Refresh token expiration window #2827
Solution 2: Revokaion-On-Use [15] #
-
Allow client retry with same refresh token
- Keep track of (1) one parent toke (RT1) and (2) a pool of child tokens.
- parent token means the latest token's
- child token means the retry-results of same parent. But we don't know if they actually successfully recieved by client.
- parent token (RT1) could generate multiple child refresh tokens and access tokens in pairs ((RT2, AT2), (RT2-1, AT2-1)...). Keep track of them.
-
Invalidate other refresh tokens AFTER (1) one of sibling refresh token or (2) the corresponding
access_token
once used.- Once
AT2-1
is used, (1) sibling (RT2, AT2), (RT2-2, AT2)... should be revoked- because it ensures client already received one of the retries.
- Once
RT2-1
is used (in next refresh), (1) sibling (RT2, AT2), (RT2-2, AT2)... should be revoked (2) (RT3, AT3) should be generated (3)RT2-1
should considered used and become parent.
- Once
-
Concurrent token READ (token introspection) and WRITE (refresh token) should be handled carefully.
Open Source OAuth Server #
- https://github.com/ory/fosite.git
- https://github.com/supertokens/supertokens-core
- https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/jose/jws/DefaultTokenManager.java