Auth0’s refresh token rotation is a security feature that automatically invalidates an old refresh token and issues a new one every time a refresh token is used.
Imagine you’re using a physical key to a secure room. If that key is lost or stolen, the room remains compromised indefinitely. Refresh token rotation is like having a security guard who, every time you use your key, checks your ID, gives you a new key, and deactivates the old one. This way, if an attacker ever got hold of your old key, it would be useless by the time they tried to use it.
Let’s see it in action. We’ll simulate a client application that has an initial refresh token and then uses it to get a new access token, triggering the rotation.
First, you need to enable this feature in your Auth0 tenant.
Navigate to your Auth0 Dashboard. Go to Applications -> APIs. Select the API for which you want to enable refresh token rotation. Go to the Settings tab. Scroll down to the Refresh Token Rotation section. Toggle the switch to Enabled.
Once enabled, any new refresh tokens issued for this API will be subject to rotation. Existing refresh tokens will also be rotated on their next use.
Now, let’s look at the client-side flow. A typical OAuth 2.0 authorization code grant with PKCE would look something like this:
-
Authorization Request: The user is redirected to Auth0’s
/authorizeendpoint to log in. -
Token Request: After successful authentication, Auth0 redirects back to your client with an authorization code. Your client exchanges this code for an access token and a refresh token using the
/oauth/tokenendpoint.curl -X POST \ https://YOUR_AUTH0_DOMAIN/oauth/token \ -H 'content-type: application/json' \ -d '{ "grant_type": "authorization_code", "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET", # If using a confidential client "code": "AUTHORIZATION_CODE_FROM_REDIRECT", "redirect_uri": "YOUR_REDIRECT_URI", "code_verifier": "PKCE_CODE_VERIFIER" # If using PKCE }'The response will contain an
access_token,expires_in,id_token, and importantly,refresh_token. -
Access Token Expiration: The
access_tokenhas a short lifespan (e.g., 1 hour). When it expires, your client needs to get a new one without requiring the user to log in again. This is where the refresh token comes in. -
Refresh Token Request (Rotation Trigger): Your client makes a request to the
/oauth/tokenendpoint using therefresh_tokengrant type.curl -X POST \ https://YOUR_AUTH0_DOMAIN/oauth/token \ -H 'content-type: application/json' \ -d '{ "grant_type": "refresh_token", "client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET", # If using a confidential client "refresh_token": "THE_REFRESH_TOKEN_RECEIVED_EARLIER" }'Crucially, with refresh token rotation enabled:
- Auth0 validates the provided
refresh_token. - If valid, Auth0 issues a new
access_token. - Auth0 then invalidates the
refresh_tokenthat was just used. - Auth0 also issues a brand new
refresh_tokenin the response.
The response will look like this:
{ "access_token": "NEW_ACCESS_TOKEN", "expires_in": 3600, "id_token": "NEW_ID_TOKEN", "refresh_token": "A_COMPLETELY_NEW_REFRESH_TOKEN", "token_type": "Bearer" }Your client application must store this new
refresh_tokenfor future use and discard the old one. If an attacker previously obtained the old refresh token, it will now be invalid, and the attacker will only have access to the newly rotated, and currently unknown to them, refresh token. - Auth0 validates the provided
The key levers you control are primarily within the Auth0 tenant settings for the API. There aren’t many direct configuration options for the rotation itself once enabled, as it’s designed to be a transparent security enhancement. The main "control" is deciding which APIs benefit from this enhanced security. You also control how your client application handles the refresh token lifecycle: it must be programmed to store the new refresh token received in the rotation response and discard the old one. Failure to do so means you’re not benefiting from the rotation and your client will eventually fail when it tries to use an invalidated token.
What most developers miss is that the refresh token itself has a lifecycle, and rotation doesn’t mean infinite refresh tokens. Auth0 enforces a maximum number of refresh tokens per user per client (defaulting to 50). If this limit is reached, older refresh tokens are automatically revoked, even if they haven’t been used. This is another layer of defense against token proliferation.
The next concept you’ll want to understand is how to manage refresh token revocation explicitly, both from the client and the administrator side.