Refresh Token Flow
Refresh tokens let you obtain new access tokens without requiring user interaction. This is essential for maintaining long-lived sessions.
Prerequisites
- You must request the
offline_accessscope during the initial authorization - Refresh tokens are only issued with the Authorization Code flow (not Client Credentials)
Token refresh request
curl -X POST https://your-sso-domain.com/connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "client_id=my-app" \
-d "client_secret=my-secret" \
-d "refresh_token=R2FtY2tqZ0hkY3BXcTk4dFZ3bE5mM2xEMkNq..."
Parameters:
| Parameter | Required | Description |
|---|---|---|
grant_type | Yes | Must be refresh_token |
client_id | Yes | Your application's client ID |
client_secret | Conditional | Required for confidential clients |
refresh_token | Yes | The refresh token from a previous token response |
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCJ9...(new)",
"token_type": "Bearer",
"expires_in": 1800,
"refresh_token": "S2p2N3RhR2FtY2tqZ0hkY3BXcTk4dFZ3bE5m...(new)",
"id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...(new)",
"scope": "openid profile email offline_access"
}
The refresh token is opaque
The refresh_token is an opaque, encrypted string – unlike the access token, it is not a readable JWT. Do not attempt to decode it or read claims from it (such as its expiration); treat it as an opaque secret, and simply store it and send it back to the token endpoint.
Token rotation
Klubero SSO implements refresh token rotation to improve security:
- Each refresh request returns a new refresh token
- The old refresh token is invalidated
- Always store and use the latest refresh token
Important
Always store the new refresh token from every response. Using an old refresh token after rotation will fail.
Token lifetimes
| Token type | Lifetime | Notes |
|---|---|---|
| Access Token | 30 minutes | Short-lived for security |
| Refresh Token | 14 days | Used to obtain new access tokens |
| Authorization Code | 5 minutes | Single-use |
When a refresh fails
Refresh tokens can become invalid because:
- The token expired (after 14 days)
- The token was revoked (the user signed out, changed their password, or an admin action)
- The session was invalidated (a security event)
- Token reuse (using an old token after rotation)
When a refresh fails, redirect the user to the authorization endpoint to re-authenticate.
Error response:
{
"error": "invalid_grant",
"error_description": "The refresh token is no longer valid."
}
Best practices
// Proactive token refresh (before expiration)
function isTokenExpired(token, bufferSeconds = 300) {
const payload = JSON.parse(atob(token.split('.')[1]));
const expiresAt = payload.exp * 1000;
return Date.now() >= expiresAt - (bufferSeconds * 1000);
}
async function ensureValidToken() {
if (isTokenExpired(accessToken)) {
try {
const newTokens = await refreshAccessToken();
accessToken = newTokens.access_token;
refreshToken = newTokens.refresh_token; // Always update it!
} catch (error) {
// Refresh failed - redirect to sign-in
redirectToLogin();
}
}
return accessToken;
}