Refresh Tokens
Refresh tokens let your application obtain new access (and ID) tokens without re-prompting the user. ChainIT issues refresh tokens from the authorization_code grant (IDP / Hosted Auth) and re-issues them from the refresh_token grant on the OAuth token endpoint.
This page covers:
- How to refresh tokens — endpoint, request parameters, sample integrations, response shape, and error codes.
- Refresh token rotation — ChainIT's default rotation behaviour, the optional compatibility mode, and the per-application toggle.
Every successful refresh_token grant returns a new refresh
token and revokes the old one — your client must persist the latest value from
each response. See Refresh token rotation if you
need to opt out for a specific OAuth app.
How to refresh tokens
Regardless of the rotation setting, clients always refresh tokens with the same OAuth 2.0 token endpoint and the refresh_token grant.
Endpoint
POST https://staging-api.chainit.online/oauth/token
Content-Type: application/x-www-form-urlencoded
The exact URL is advertised by the server metadata document as token_endpoint.
Request parameters
| Parameter | Required | Description |
|---|---|---|
grant_type | ✅ | Must be the literal string refresh_token. |
refresh_token | ✅ | The refresh token previously issued to this client_id. |
client_id | ✅ | OAuth client ID that received the refresh token. |
scope | – | Optional. A space-separated subset of the originally granted scopes. |
The refresh_token grant does not require client_secret,
even for confidential clients (M2M, Hosted Auth, IDP server-side). The refresh
token itself is the proof of possession — it is bound to the issuing
client_id and is single-use when rotation is enabled. Use
client_secret only on the authorization_code and
client_credentials grants.
You receive the refresh_token from either the authorization_code
exchange (IDP / Hosted Auth) or a previous refresh_token grant. For
rotation-enabled apps, always persist the latest value returned and discard
the one you sent.
Sample integration
curl
curl -X POST "https://staging-api.chainit.online/oauth/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "client_id=<<your_client_id>>" \
-d "refresh_token=<<your_refresh_token>>"
Node.js
import axios from "axios";
const params = new URLSearchParams({
grant_type: "refresh_token",
client_id: process.env.CLIENT_ID,
refresh_token: currentRefreshToken,
});
const { data } = await axios.post(
"https://staging-api.chainit.online/oauth/token",
params,
{ headers: { "Content-Type": "application/x-www-form-urlencoded" } },
);
// Persist the latest refresh_token — required when rotation is enabled.
saveTokens(data.access_token, data.refresh_token, data.id_token);
Python
import requests
response = requests.post(
"https://staging-api.chainit.online/oauth/token",
data={
"grant_type": "refresh_token",
"client_id": CLIENT_ID,
"refresh_token": current_refresh_token,
},
)
tokens = response.json()
# Persist the latest refresh_token — required when rotation is enabled.
save_tokens(tokens["access_token"], tokens["refresh_token"], tokens.get("id_token"))
Success response
The response shape is identical in both modes — only the refresh_token value differs.
Rotation enabled (default):
{
"access_token": "<<new_access_token>>",
"refresh_token": "<<new_refresh_token>>",
"id_token": "<<new_id_token>>",
"expires_in": 3600,
"token_type": "Bearer"
}
Rotation disabled:
{
"access_token": "<<new_access_token>>",
"refresh_token": "<<same_refresh_token_submitted_on_request>>",
"id_token": "<<new_id_token>>",
"expires_in": 3600,
"token_type": "Bearer"
}
Errors
All error responses follow the OAuth 2.0 shape defined by RFC 6749 §5.2:
{
"error": "invalid_grant",
"error_description": "Refresh token is invalid or expired"
}
| HTTP | Code | Reason |
|---|---|---|
| 400 | invalid_request | A required parameter is missing — grant_type, refresh_token, or client_id. |
| 400 | invalid_client | client_id is unknown to the authorization server. |
| 400 | invalid_grant | Refresh token is invalid, expired, or revoked. |
| 400 | invalid_grant | Refresh token does not belong to the supplied client_id. |
| 400 | invalid_grant | Reuse detected — an already-rotated refresh token was submitted; the entire family is now revoked. |
| 400 | invalid_scope | Requested scope is not a subset of the client's allowed scopes (or of the originally granted scopes, when known). |
Refresh-token grant failures for a known client_id are recorded in OAuth activity logs as TOKEN_VALIDATION_FAILED with metadata including grantType, tokenType, oauthError, and oauthErrorDescription. Reuse and client-mismatch cases also emit security logs.
Refresh token rotation
Refresh-token rotation controls what ChainIT returns when a client calls /oauth/token with grant_type=refresh_token. It is enabled by default and aligns with the OAuth 2.0 Security BCP — rotation limits the lifetime of a stolen refresh token and enables automatic replay detection.
Default behaviour: rotation enabled
When rotateRefreshTokens is true:
- The submitted refresh token is validated.
- ChainIT revokes the submitted refresh token.
- ChainIT blacklists the old token group so previously issued access tokens stop working.
- ChainIT returns a new access token, a new refresh token, and an ID token when applicable.
Clients must persist the latest refresh_token from every successful refresh response. Reusing an older refresh token is treated as refresh-token reuse and can revoke the session.
If a previously rotated (already revoked) refresh token is submitted, ChainIT treats it as a possible theft replay: the entire refresh-token family for that user and client is revoked and the affected session must re-authenticate.
Compatibility mode: rotation disabled
Some clients cannot safely persist a newly returned refresh token after every refresh call. For those clients, disable rotation on the OAuth application.
When rotateRefreshTokens is false:
- The submitted refresh token is validated.
- ChainIT returns a new access token and ID token when applicable.
- ChainIT returns the same refresh token value that was submitted.
- Previously issued access tokens are not blacklisted by the refresh call; they expire naturally.
Disabling rotation is a compatibility option. It removes automatic replay detection for normal refresh-token reuse, so only use it for clients that cannot reliably store rotated refresh tokens.
Configure the toggle
Configure rotateRefreshTokens from the Developer Portal:
- Open the application in Applications.
- Open Advanced Settings → Refresh Token Rotation.
- Toggle rotation on or off and save.
If you leave the toggle untouched on a newly created application, rotation defaults to on (true).
See Create application — step-by-step for the full registration walkthrough.
Next steps
- Token management overview
- Token revocation (RFC 7009)
- RP-Initiated Logout — full session sign-out
- Security best practices