Token lifetimes & refresh
A login yields short-lived ID and access tokens and, when you ask for it, a long-lived refresh token to mint fresh ones without sending the user back through the login page. The defaults are sensible; you can tune them per client within bounds.
Defaults
| Token | Default lifetime | What it is |
|---|---|---|
| ID token | 1 hour | The signed identity token — your app's proof of who logged in. |
| Access token | 1 hour | Bearer token for userinfo; for an API resource, a per-resource JWT with its own TTL. |
| Refresh token | 14 days | Opaque token to obtain new access/ID tokens without re-login. |
| Authorization code | 10 minutes | Single-use, exchanged at the token endpoint. |
The ID and access tokens are deliberately short — an hour bounds the blast radius of a leaked token, and a refresh token (or a fresh login) replaces them silently.
Getting a refresh token
A refresh token is gated, not automatic. You get one only when all of these hold:
- The client is allowed the
refresh_tokengrant. - The authorization request includes the
offline_accessscope. - The request also carries
prompt=consent.
Drop any one and no refresh token is issued. This is the same gate described in Scopes & claims; the curl guide shows the exact request parameters.
Tuning per client
An owner can override a client's ID-token and refresh-token lifetimes with update_client_token_ttls, within bounds that mirror the IDP's own clamp exactly:
| Override | Min | Max |
|---|---|---|
id_token_ttl | 60 s | 14,400 s (4 h) |
refresh_token_ttl | 60 s | 4,838,400 s (56 days) |
The ceilings are 4× the defaults — a misconfigured value can never mint a year-long token. The bounds are enforced at the management API, so a value outside the range is rejected (400), not silently clamped: what you set is what the IDP honors.
You can shorten a TTL freely down to the 60-second floor, but you can only lengthen it up to 4× the default. If you ask for a longer id_token than 4 hours or a longer refresh than 56 days, the request is refused — raise a shorter value or reconsider the design rather than expecting a clamp.
Using and revoking a refresh token
Exchange a refresh token at the token endpoint (grant_type=refresh_token) to get a new access token and ID token. auth.is rotates the refresh token on every use: each exchange returns a new refresh_token value and retires the one you sent. Always store the value from the latest response and send that one next time — the previous value is now spent.
This gives you automatic reuse detection. A refresh token is single-use, so if a spent value is ever presented again — the tell-tale sign that a token was stolen and replayed — auth.is does not just reject that one request: it revokes the entire grant family (every access and refresh token issued from that authorization). The legitimate client, whose newest token was also part of that family, is signed out and must re-authenticate. A leaked refresh token is therefore useful to an attacker for at most one exchange before the whole line is burned down. (This is stricter than phase 1, which left refresh tokens reusable until expiry.)
There is no grace window: two requests racing on the same refresh value will trip reuse detection, so a client must persist each rotated value before issuing the next refresh rather than refreshing the same token concurrently.
Revocation is also immediate and server-side. Post a token to the revocation endpoint (RFC 7009) — for example on logout of a single device — and it stops working at once:
POST https://{slug}.auth.is/oidc/token/revocation token=<refresh_or_access_token>&token_type_hint=refresh_token
A password reset revokes the account's refresh tokens wholesale as part of sweeping its sessions and grants — see Sessions & SSO. The curl guide §7–8 walks refresh and revocation with real commands.
API access tokens
An access token minted for an API resource is a JWT signed with the issuer's keys, and it uses that resource's configured access-token TTL (default 1 hour), independent of the client's id/refresh overrides. A plain userinfo access token (no resource indicator) is opaque and uses the 1-hour default.
Exchanged tokens (first-party service exchange)
When you call the management API through the MCP server, the MCP does not forward your token — it trades it at the token endpoint (RFC 8693 token exchange) for a fresh access token audience-bound to the API it is about to call. Each auth.is API accepts only its own audience; the exchange is what bridges them. Exchanged tokens have their own lifetime rules:
- TTL = min(the target resource's access-token TTL, the subject token's remaining lifetime). A derived credential never outlives the token it derives from — exchange a token with ten minutes left and the result lives at most ten minutes.
- No refresh token, ever. When an exchanged token expires, the service simply re-exchanges — which requires the original (subject) token to still be valid. Kill the source and the derivation chain dies with it at the next exchange.
- No per-token revocation once issued. Like every per-resource access token here, an exchanged token is a stateless JWT verified offline against the issuer's keys, so there is no server-side record to revoke. The exposure is bounded by the TTL cap above plus strict scope attenuation (the exchanged scope is always a subset of the subject's — never widened): an exchanged token is never more powerful, nor longer-lived, than presenting the subject token directly.
- Impersonation, not delegation (RFC 8693 §1.1). The exchanged token keeps the subject's identity unchanged and carries no
act/may_actclaims — the API sees you, with the exchanging service invisible in the token.
Exchange is a first-party mechanism between auth.is services (only confidential, first-party service clients may call the grant, and only along seeded pairings). Customer and DCR-registered clients cannot use it.
Device sign-in (no browser on the device)
For an input-constrained or headless device — a CLI, a TV app, an agent that cannot open a browser — auth.is supports the Device Authorization Grant (RFC 8628). Add urn:ietf:params:oauth:grant-type:device_code to the client's grant_types, then:
- The device POSTs to the device authorization endpoint (
/device/auth) and gets back auser_code, averification_uri, and adevice_code. - It shows the user the short
user_codeand asks them to open theverification_uri(published asdevice_authorization_endpoint/code_verificationin discovery) in any browser — on their phone, say. They enter the code, confirm, and sign in through the normal login → consent chain. The device itself never handles the user's credentials. - Meanwhile the device polls the token endpoint (
grant_type=urn:ietf:params:oauth:grant-type:device_code+ thedevice_code), honoring theintervaland backing off onslow_down. Once the human approves, the poll returns the same ID / access / refresh tokens a browser login would — with the same lifetimes and the sameoffline_accessgate for a refresh token.
The device_code is valid for 10 minutes — the window the user has to approve — after which polling returns expired_token and the device must start over. Because the user_code is short and human-typeable, the verification page is per-IP rate-limited against guessing.
Personal access tokens (headless CI)
When there is no human at all — a CI job, a cron task, a build agent — the device grant's single browser approval is still one approval too many. For that case auth.is issues Personal Access Tokens (PATs): long-lived, team-scoped, revocable credentials you mint once and hand to the machine.
A PAT is bound to exactly one team and carries the mgmt management scope — the same subset the management API accepts from an interactive login, confined to that one team. It can never carry admin (the operator surface is unreachable from a PAT), and it can never mint or revoke PATs (no self-propagation — those two operations require a real interactive login).
Mint one through the management API (mint_pat) or the dashboard. The response contains the plaintext token exactly once:
authis_pat_Xm9dQ2... # shown once — store it now, it cannot be retrieved again
Only a SHA-256 hash of the token is stored server-side, alongside a 4-character last4 tail so you can recognise it in a list. Send it as a normal bearer token to the management API:
Authorization: Bearer authis_pat_<value>
| Property | Value |
|---|---|
| Prefix | authis_pat_ (32 bytes of entropy, base64url) |
| Scope | mgmt, confined to the bound team — never admin |
| Expiry | 90 days by default; you may set any expiry up to 1 year |
| At rest | SHA-256 hash only (never the raw value); last4 for display |
| Revocation | Immediate — the token’s creator or a team owner may revoke it |
A PAT is fixed-TTL: there is no refresh. When it nears expiry, mint a new one and revoke the old. Treat the value like a password — anyone holding it can act as you within that one team until it expires or is revoked, so store it in your CI secret manager, never in source. Failed PAT lookups are per-IP rate-limited, so a leaked-and-rotated token can't be brute-forced by prefix. Revoke immediately on any suspicion of leak; revocation takes effect on the next request.