CodamAIDocs
Topicdone

Renew the token

When and how the token is renewed before it expires, and what happens when a refresh is rejected or fails.

Variants
Refresh successfulRefresh rejected (4xx) → sign in againKeycloak not reachable (5xx) → retry later

What this is about

An access token is valid only for a short time. In Keycloak, the default is 5 minutes. So that no one has to sign in again every few minutes, the BFF gets a new one in time. For this, it sends the refresh token to Keycloak. This is called a refresh.

Why are access tokens so short? An access token is only checked locally on every request. No one asks Keycloak. So a revoked permission only disappears when a new token is issued. Short tokens keep this gap small. See Why revoking permissions takes effect with a delay.

The timeline

gantt
    title One access token (default 5 minutes)
    dateFormat mm:ss
    axisFormat %M:%S
    section Token 1
    valid                         :t1, 00:00, 5m
    Buffer, refresh due           :crit, r1, 03:30, 90s
    section Token 2
    valid after the refresh       :t2, 03:30, 5m

From 90 seconds before expiry, the token counts as “expiring soon”. The next session request then triggers the refresh. The user interface requests the session every minute and every time the tab comes back to the foreground. So the refresh almost always happens within the buffer.

How the refresh works

Refresh in the BFF
  1. 1
    Browser→BFF
    requests /api/auth/session (every minute and on focus)
  2. 2
    BFF
    Does the access token expire in less than 90 seconds?
    no: session returned unchanged
  3. 3
    BFF→Keycloak
    POST /token with grant_type=refresh_token, client ID and client secret
  4. 4
    Keycloak→BFF
    new access token, usually also a new refresh token and ID token
  5. 5
    BFF
    writes the new tokens into the session cookie
    Result: Session continues, no one notices anything

Keycloak issues a new refresh token on every refresh. If the realm is set up so that a refresh token is valid only once, the old one becomes invalid. This is called rotation.

The outcomes

What can happen during a refresh

When: Keycloak responds with new tokens.

The BFF takes over the access, refresh and ID token and clears any error field in the session. If no new refresh token comes along, it keeps the old one.

Result: Session continues.

When: Keycloak rejects, usually with invalid_grant. The refresh token has expired, the Keycloak session was ended, or the account was suspended.

  1. 1
    BFF
    sets the error RefreshAccessTokenError in the session
  2. 2
    BFF→Browser
    API calls of the BFF now respond with 401
  3. 3
    Frontend
    sees the error and redirects to /login
  4. 4
    Browser→Keycloak
    new login. If there is no session at Keycloak anymore, Keycloak asks for the password

Result: The old session is over. The new login overwrites the cookie.

When: Keycloak responds with 5xx or not at all.

The BFF keeps the current token and sets no error. The next session request tries again. The 90-second buffer leaves time for this before the token really expires.

Result: Short Keycloak outages go unnoticed. If the outage lasts longer than the token, the APIs respond with 401.

When: Keycloak responds with 5xx or not at all.

The hub treats every failed refresh like a rejection. It removes the access token and sets RefreshAccessTokenError.

Result: The user interface starts a new login. As long as Keycloak is not reachable, it does not succeed.

Special behavior in the hub

The hub also renews before every API call. If the access token has already expired (with a 30-second safety margin), the BFF first gets a new one. The new token applies only to this call. It goes into the cookie on the next session request.

If Keycloak rotates the refresh token, two simultaneous calls must not renew twice with the same refresh token. The second one would get invalid_grant. So the hub remembers for 60 seconds which renewals are running or have just run, and lets parallel calls wait for the same result.

And API clients without a user interface?

Your own client that calls CDMS directly does the same: renew with the refresh token before expiry. If it gets 401 with invalid_token from the API, the token has expired: renew it and repeat the request. See Access without a token.

Next

Sources in the code and the knowledge base
  • hub-frontend, CIAS/cias-frontend, CDMS/frontend – server/api/auth/[...].ts (jwt callback, REFRESH_BUFFER_MS), server/utils/refreshToken.ts
  • hub-frontend – server/utils/sessionToken.ts (isExpired, renewal per call), refreshToken.ts (RENEWAL_MEMORY_MS)
  • hub-frontend, CIAS/cias-frontend, CDMS/frontend – app/middleware/auth.global.ts, app/pages/login.vue (RefreshAccessTokenError)
  • CIAS/cias-runtime/deploy/keycloak/import/codamai-realm.json (no custom token lifetimes)
Search