Token & Session Lifecycle
How Curupira issues, refreshes, validates, and ends tokens and sessions. This is the reference for resource-server authors and anyone integrating beyond the basic login flow — for the initial wiring see Add login to your app.
The OpenID discovery document is the authoritative source for every endpoint and supported capability — fetch it rather than hard-coding.
Tokens at a glance
| Token | Format | Default lifetime | Purpose |
|---|---|---|---|
| Access token | signed JWT | 1 hour (DEFAULT_ACCESS_TTL_SECS, 3600) | Sent to your APIs; carries sub and the permissions claim. |
| Refresh token | opaque string | 30 days (DEFAULT_REFRESH_TTL_MINS, 43200) | Exchanged for a new access token; rotated on every use. |
| ID token | signed JWT (OIDC) | issued with the access token | Identity of the user for the client app. |
Lifetimes are deployment-configurable; always trust the expires_in in the token response rather than
assuming a fixed value.
Getting tokens (authorization code grant)
Tokens are issued at POST /oauth2/token:
- Public clients (SPAs, mobile) use PKCE — no client secret.
- Confidential clients (server-side) authenticate with their API key.
The full authorize → callback → token exchange is covered in Add login to your app.
Refreshing an access token
When the access token expires, exchange the refresh token for a new one:
curl -X POST https://auth.curupira.api.br/oauth2/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=refresh_token' \
-d 'refresh_token=<current-refresh-token>' \
-d 'client_id=<your-client-id>' \
-d 'api_key=<your-api-key>'
:::warning Refresh tokens are rotated
Every refresh returns a new refresh token and invalidates the one you just used. Your client must
store the new refresh token from each response and discard the old one — reusing a consumed refresh
token fails. Rotation limits the damage of a leaked token. (The refresh_token grant requires the
application's API key, so it's for confidential clients.)
:::
An unsupported grant_type returns unsupported_grant_type; an invalid/expired refresh token returns
invalid_grant.
Validating tokens (resource servers)
Your API can validate Curupira access tokens two ways:
1 · Verify the JWT locally (recommended, stateless)
Fetch Curupira's public signing keys from the JWKS endpoint and verify the token's signature,
exp, iss, and aud, then authorize on the permissions claim:
GET https://auth.curupira.api.br/.well-known/jwks.json
Each application signs with its own key, identified by the token header's kid — select the
matching key from the JWKS by kid. Fetch the JWKS dynamically and cache it (don't pin a single
key): signing keys can rotate, and a client that hard-codes a key breaks on rotation.
2 · Token introspection (RFC 7662)
For opaque validation, ask Curupira directly:
curl -X POST https://auth.curupira.api.br/oauth2/introspect \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'token=<access-token>'
The response is always 200; an unknown, expired, or malformed token is reported as
{"active": false} (per spec), and a valid one as {"active": true, …} with its claims. Prefer local
JWT verification for hot paths — introspection adds a network round-trip per check.
Ending a session
GET https://auth.curupira.api.br/oauth2/logout
Terminates the user's Curupira session. If you registered post-logout redirect URIs for the application, you can return the user to one of them afterwards (see Tenant Onboarding for registering them).
User info
GET /oauth2/userinfo (with a valid access token) returns the standard OIDC claims for the
authenticated user — useful when you want profile data without decoding the ID token yourself.
Next steps
- Add login to your app — the authorization-code flow that starts it all.
- Roles & Permissions — what the
permissionsclaim carries.