Skip to main content

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_access scope 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:

ParameterRequiredDescription
grant_typeYesMust be refresh_token
client_idYesYour application's client ID
client_secretConditionalRequired for confidential clients
refresh_tokenYesThe 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 typeLifetimeNotes
Access Token30 minutesShort-lived for security
Refresh Token14 daysUsed to obtain new access tokens
Authorization Code5 minutesSingle-use

When a refresh fails​

Refresh tokens can become invalid because:

  1. The token expired (after 14 days)
  2. The token was revoked (the user signed out, changed their password, or an admin action)
  3. The session was invalidated (a security event)
  4. 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;
}