Refresh tokens

Access tokens issued by Truora Pass expire after 60 minutes by default. The Authorization Code flow also issues a refresh_token, which lets your backend obtain fresh access tokens without sending the user through authorization again.

Refresh tokens are long-lived (365 days by default), single-use, and rotated on every exchange.

Note: every Authorization Code token exchange returns a refresh token, so any application using that flow can use this grant. The CIBA flow does not issue refresh tokens.

Exchanging a refresh token

Send a JSON request to the token endpoint:

curl -X POST https://api.pass.truora.com/v1/oauth2/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "refresh_token": "<your most recent refresh token>",
    "client_id": "WLT_APP_your_client_id",
    "client_secret": "your_client_secret"
  }'
Field Required Description
grant_type Yes Must be refresh_token.
refresh_token Yes The most recently issued refresh token.
client_id Yes Your application’s client ID — must be the client the token was issued to.
client_secret Yes Your application’s client secret.
scope No Space-delimited subset of the originally granted scopes, to narrow the new access token.

On success:

{
  "access_token": "<new access token>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid profile email",
  "refresh_token": "<NEW refresh token — replace the old one>"
}

Note: the refresh grant never returns an id_token. Read up-to-date identity claims from GET /v1/oauth2/userinfo with the new access token — see UserInfo and claims.

Rotation: every exchange returns a new token

Refresh tokens are single-use. Every successful exchange consumes the token you sent and returns a new one belonging to the same token family. Always persist the refresh_token from the latest response and discard the previous one — the old token cannot be used again.

The token lifetime slides with rotation: each successful exchange gives the new token a full lifetime (365 days by default) from the moment of rotation. A user who returns at least once a year therefore never needs to re-authorize; a family that goes unused for the full lifetime expires.

Reuse detection

If a refresh token that was already used is presented again, Truora Pass treats it as a possible token leak and revokes the entire token family — including the currently valid newest token. The exchange fails with invalid_grant (refresh token already used), and every subsequent exchange in that family fails too.

To recover, send the user through the Authorization Code flow again to obtain a new grant.

Note: reuse detection means a buggy retry (for example, replaying a request after a timeout whose original attempt actually succeeded) can revoke your tokens. Persist the new refresh token atomically with marking the old one as used on your side.

Client binding

A refresh token can only be exchanged by the application it was issued to. Presenting it with a different client_id fails with invalid_grant (refresh token was not issued to this client).

Down-scoping

The optional scope field lets you request an access token with fewer scopes than originally granted:

{
  "grant_type": "refresh_token",
  "refresh_token": "<your most recent refresh token>",
  "client_id": "WLT_APP_your_client_id",
  "client_secret": "your_client_secret",
  "scope": "openid email"
}

The requested scopes must be a subset of the scopes in the original grant; anything outside that set fails with invalid_scope. Omitting scope keeps the originally granted scopes.

Errors

This grant answers errors in the standard OAuth 2.0 (RFC 6749) envelope, {"error": "...", "error_description": "..."}:

HTTP error When
401 invalid_client Wrong client_id / client_secret.
400 invalid_grant The refresh token is invalid or expired; was not issued to this client; or was already used (reuse detected — the whole family is revoked).
400 invalid_scope The requested scope is not a subset of the originally granted scopes.

On invalid_grant, discard your stored tokens for that user and re-run the Authorization Code flow.