Skip to content
CalabDocs

Tokens and lifetimes

Formats and lifetimes of codes, access, refresh and ID tokens, refresh rotation and why access can end early.

Every provider token except the ID token is opaque: 32 random bytes in base64url behind a type prefix. Calab stores only their SHA-256 hashes, so a token can never be recovered or shown again.

At a glance

WhatFormatLifetimeNotes
client_idcalab_client_ + 43 charactersUntil the client is deletedA public identifier, not a secret
Client secretcalab_os_ + 43 charactersNo expiry; after a rotation the old one stays valid for 10 minutesShown once
Sign-in request—10 minutesThe consent screen must finish within it
Authorization codecalab_oc_ + 43 characters60 seconds, single useBound to the client, redirect_uri and PKCE
Access tokencalab_oa_ + 43 charactersup to 5 minutes (expires_in)For /userinfo only
Refresh tokencalab_or_ + 43 characters8 hours at most, 30 minutes idleRotated on every exchange
ID tokenJWT, RS256Same as the access tokenVerified with jwks_uri

Access token

An access token opens /userinfo and nothing else. It does not work with the Calab REST API, the gateway or voice; conversely, Calab app tokens and bot tokens are refused by /userinfo. There is no introspection: to check a token, call /userinfo — 401 invalid_token means it is no longer valid.

Refresh tokens and rotation

A refresh token is issued only when the client has “Allow continued access” turned on and the person kept that box ticked on the consent screen.

  • Every exchange (grant_type=refresh_token) returns a new refresh token, a new access token and a new ID token (without nonce). The old refresh token becomes used immediately.
  • Presenting an already used refresh token again from the same client is treated as a leak: Calab revokes the whole chain (the grant) and the person must sign in again. There is no grace window — if a response is lost on the network, a new sign-in is required.
  • Each exchange extends the idle deadline to 30 minutes, but never past the absolute 8-hour limit from sign-in.
  • An exchange can narrow scopes (the scope parameter); the narrowing applies to the whole chain. Scopes can never be widened that way (invalid_scope).

ID token

A JWT with alg: RS256 and a kid header. Claims: iss, sub, aud (= your client_id), iat, exp, auth_time, nonce (code exchange only), plus name with the profile scope and email + email_verified: true with the email scope and a verified address. An unverified email never appears in the ID token or in /userinfo.

Signing keys are configured by the installation's operator; new keys are published in jwks_uri ahead of use and old ones stay there during the transition. Cache the JWKS (the server sends Cache-Control: max-age=60) and reload it once when you meet an unknown kid.

Subject (sub)

  • A random identifier (32 bytes, base64url) created for the “workspace + member” pair.
  • Stable: it does not change when the name or email changes or the client is recreated.
  • The same for every client of one workspace and different across workspaces (their issuers differ too).
  • It never reveals the internal account UUID.

Use the (iss, sub) pair as the account key in your application.

When access ends early

Calab re-checks everything on every code exchange, refresh and /userinfo call. Access (the whole grant chain) ends when:

  • the person revokes it in “OAuth apps” or signs out of the Calab session they signed in from;
  • the member is removed or banned from the workspace, or the directory (AD) closes their access;
  • an admin changes the client's redirect URIs, origins, scopes or refresh setting, rotates its secret or deletes it;
  • the workspace's SSO policy changes or the corporate sign-in proof expires;
  • the Business / Enterprise plan ends or the workspace is suspended;
  • the client presents a used code or refresh token again.

At /token this shows up as 400 invalid_grant, at /userinfo as 401 invalid_token. The right reaction is the same: start a new sign-in.