Refresh tokens
Keep users signed in beyond the access token lifetime with OneiD refresh tokens, and handle rotation, reuse and failures correctly.
A refresh token lets your application get a new access token without sending the user back to OneiD. OneiD rotates refresh tokens on every use and ends a chain of refreshes when the original lifetime runs out. This guide shows how to request, use, store and revoke them.
Get a refresh token
OneiD issues a refresh token when both are true:
- the client has the refresh token grant, set by your administrator, and
- the authorisation request includes the
offline_accessscope.
GET /connect/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&scope=openid%20profile%20offline_access&state=hX3n9qT2vYw8LkP0&nonce=Qm4rT8zWc1JpVb6s&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256 HTTP/1.1
Host: YOUR_ONEID
The token response after the code exchange then contains refresh_token. See Authorization code flow with PKCE for the full flow.
If the client does not have the refresh token grant, or you leave out offline_access, there is no refresh token. Client credentials never return one.
Refresh tokens are opaque. Do not parse them, and never send them to an API.
Use a refresh token
Send it to the token endpoint with grant_type=refresh_token.
A public client sends its client ID:
POST /connect/token HTTP/1.1
Host: YOUR_ONEID
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=EXAMPLE_OPAQUE_REFRESH_TOKEN_7Hq2Lm9Xv4Rk
&client_id=YOUR_CLIENT_ID
A confidential client authenticates as it does for the code exchange:
curl -s https://YOUR_ONEID/connect/token \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-d grant_type=refresh_token \
-d refresh_token=EXAMPLE_OPAQUE_REFRESH_TOKEN_7Hq2Lm9Xv4Rk
The response contains a new access token and a new refresh token:
{
"access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IkVYQU1QTEVfS0lEIn0.EXAMPLE_ACCESS_TOKEN_PAYLOAD.EXAMPLE_SIGNATURE",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid profile offline_access",
"refresh_token": "EXAMPLE_OPAQUE_REFRESH_TOKEN_Np5Wc8Ds1Ky3"
}
Checkpoint The
refresh_tokenin the response differs from the one you sent. Your application now stores the new value and discards the old one.
Rotation: always store the new refresh token
Every successful refresh returns a new refresh token. The one you sent is now used. Replace it in your store before you do anything else with the response.
If the same refresh token is used again after a short grace period of about 30 seconds, OneiD:
- refuses the request with
invalid_grant, and - revokes the whole chain, including the newer refresh token issued from it.
The user must then sign in again. This protects users when a refresh token is stolen: either the thief or your application uses it second, and the chain ends.
Avoid accidental reuse
Reuse usually comes from your own application refreshing twice at once, for example two browser tabs or two worker threads that notice an expired access token at the same moment.
- Serialise refreshes. Let one caller refresh, and let the others wait for its result. In a browser application, coordinate tabs, for example with a lock or by keeping tokens in one tab.
- Write the new token before you release the lock. A second caller must read the new refresh token, not the one that was just used.
- Do not rely on the grace period. It covers a lost response or a retry within a few seconds. It is not a design for parallel refreshes.
- Refresh shortly before expiry, not on every request. Use
expires_into know when the access token runs out.
Lifetime: not sliding
Refresh tokens last 14 days by default; your administrator can change this per client. The lifetime is not sliding: each new refresh token in a chain expires when the original one would have. When the chain reaches the end, the refresh fails with invalid_grant and the user signs in again.
Plan for this. Users of an application they open every day still see a sign-in at the end of the refresh token lifetime. If they still have a OneiD session, that sign-in completes without a password prompt.
Each refresh checks the user
OneiD re-checks the user on every refresh. The refresh fails with invalid_grant when the user:
- has been deleted
- is locked
- must change their password
- must enrol in MFA
OneiD also revokes refresh tokens itself when the user’s password changes, an administrator removes a role, locks the user, resets the user’s MFA or sets a temporary password, and when the user signs out of your application. A refresh after any of these fails with invalid_grant.
Handle a failed refresh
Treat invalid_grant from a refresh as “this session is over”:
- Delete the stored refresh token and access token.
- End the user’s session in your application.
- Send the user to sign in again with a new authorisation request.
Do not retry the same refresh token. Retrying cannot succeed, and after the grace period it counts as reuse.
Other responses:
| Response | Meaning | What to do |
|---|---|---|
invalid_grant |
Expired, used, revoked, or the user can no longer sign in. | Sign the user in again. |
invalid_client |
Wrong secret, expired secret or disabled client. | Fix the configuration; ask your administrator. |
HTTP 429 with slow_down |
Too many requests. | Wait for Retry-After seconds. |
See Errors and troubleshooting.
Revoke on sign-out
When the user signs out, end the refresh token too. Two ways do this:
- Sign out through OneiD. Signing out with the end session endpoint revokes your application’s tokens for that user. See Sign-out.
- Revoke the token directly. If your application only clears its own session, revoke the refresh token first:
POST /connect/revoke HTTP/1.1
Host: YOUR_ONEID
Authorization: Basic WU9VUl9DTElFTlRfSUQ6WU9VUl9DTElFTlRfU0VDUkVU
Content-Type: application/x-www-form-urlencoded
token=EXAMPLE_OPAQUE_REFRESH_TOKEN_Np5Wc8Ds1Ky3&token_type_hint=refresh_token
A public client sends client_id=YOUR_CLIENT_ID in the body instead of the Authorization header.
Storage
- Server-side applications: keep refresh tokens on the server, for example in the session store or an encrypted database column. Never send them to the browser.
- Browser applications: keep tokens in memory. Avoid
localStoragefor refresh tokens, because any script on the page can read it. See Browser applications and CORS. - Mobile and desktop applications: use the platform’s secure storage, such as the iOS Keychain or Android Keystore.