# Refresh tokens

> Keep users signed in beyond the access token lifetime with OneiD refresh tokens, and handle rotation, reuse and failures correctly.

Source: https://oltinid.com/docs/guides/refresh-tokens/ · Section: Guides · All OneiD documentation: https://oltinid.com/llms.txt

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_access` scope.

```http
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](https://oltinid.com/docs/guides/authorization-code-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:

```http
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:

```bash
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:

```json
{
  "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_token` in 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:

1. refuses the request with `invalid_grant`, and
2. 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_in` to 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":

1. Delete the stored refresh token and access token.
2. End the user's session in your application.
3. 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](https://oltinid.com/docs/reference/errors/).

## 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](https://oltinid.com/docs/guides/logout/).
- **Revoke the token directly.** If your application only clears its own session, revoke the refresh token first:

```http
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 `localStorage` for refresh tokens, because any script on the page can read it. See [Browser applications and CORS](https://oltinid.com/docs/guides/browser-applications/).
- **Mobile and desktop applications:** use the platform's secure storage, such as the iOS Keychain or Android Keystore.

## Learn more

- [Sign-out](https://oltinid.com/docs/guides/logout/)
- [Tokens](https://oltinid.com/docs/reference/tokens/)
- [Sessions, prompt and max_age](https://oltinid.com/docs/guides/sessions-and-reauthentication/)
