Tokens & sessions
Zynth Auth issues two RS256-signed JWTs on sign-up, login, and refresh. The same token pair is issued regardless of how the user authenticated — password, password + a TOTP MFA challenge, or a passkey (WebAuthn). A user-verified passkey is two factors in one step, so it is not additionally challenged for a code.
Access token
Short-lived (default 15 minutes), sent as Authorization: Bearer <token> on each
request. It carries minimal claims — identity, not permissions:
| Claim | Meaning |
|---|---|
sub | User ID |
tid | Tenant (organization) ID |
sid | Session ID — the refresh-token family this token belongs to; used for server-side revocation |
type | access |
iss | Issuer — zynth-auth, the access-token issuer. (The OIDC id_token uses a different issuer — see below.) |
aud | Audience — zynth-services, the platform default. Tokens minted for a registered OAuth resource instead carry that resource's identifier and verify only there. |
email | User email — present on sign-up/login tokens; not re-included on tokens minted by /refresh |
iat / exp | Issued-at / expiry |
jti | Unique token ID |
Treat every claim except
sub/tidas convenience metadata.
The JWT header carries a kid (key ID) identifying which signing key to verify against.
Permissions are not in the token. Roles/permissions are resolved server-side on each request so access changes take effect immediately. Don't make authorization decisions from token claims alone — treat the token as proof of identity.
Refresh token
Longer-lived (default 7 days for workforce users, 30 days for CIAM/customer users),
used only to obtain a new token pair via
POST /api/v1/auth/refresh. It has no aud and is
revocable server-side. Refresh re-checks tenant membership before issuing new tokens.
Body vs. cookie transport
By default the refresh token is returned in the JSON body — the right choice for SDK/API
clients. Browser clients should instead send use_cookie: true on
signup/login/mfa-challenge/change-password: the refresh token is then set as an httpOnly,
Secure, SameSite=Strict cookie scoped to /api/v1/auth (and the body's refresh_token
is null), so JavaScript — and therefore an XSS payload — can never read the long-lived
credential. In cookie mode call refresh with an empty body; the cookie authenticates and is
rotated on every refresh. See Cookie transport.
Rotation, reuse detection & revocation
Refresh tokens are rotated: every call to /refresh consumes the token you present and
returns a brand-new refresh token. A refresh token is single-use — persist the new one
and discard the old.
- Reuse detection: presenting an already-used refresh token (a strong signal it was stolen) revokes the entire session — every token in it stops working and the user must sign in again.
- Logout is immediate:
POST /api/v1/auth/logoutrevokes the session server-side. Because access tokens are checked against the session on every request, the outstanding access token stops working at once, not only when it would have expired. - A password change or reset invalidates everything issued earlier. Every token —
access and refresh, across all devices — with an
iatbefore the password change is rejected.change-passwordreturns a fresh pair so the current device stays signed in; after areset-passwordthe user logs in again. - Sign out everywhere / instant global revocation. A single lever — a per-user
revocation epoch — invalidates every token on every device at once, with no session
enumeration. It backs
POST /auth/sign-out-alland also fires automatically on the directory-driven events below. The same instant kill applies whether the trigger is the user, your support team, or the tenant's identity provider. - Directory-driven revocation (enterprise). When a user is deprovisioned or logged out through the tenant's IdP, their Zynth sessions end immediately via that same lever: SCIM deactivation/deletion, SAML single logout, and OIDC back-channel logout all revoke on the spot. See Directory provisioning and the enterprise SSO guides.
- The platform itself, in response to a security signal. Reuse detection above revokes the one session it caught. Separately, the autonomous-response engine can pull the global revocation lever for a user on a signal such as refresh-token reuse — every device, at once. It is off by default and never turns itself on: your organization starts at Manual for every response class, and acting without a human additionally requires a track record the class has to earn. But once you have raised a level, this is a way a user's sessions can end with no person having clicked anything, so it belongs on this list. Every such action is in the decision ledger, notifies when unattended, and can be undone — reverting restores the previous state, so devices resume working without a fresh sign-in.
Tokens minted by the OpenID Provider
When Zynth Auth acts as an OpenID Provider — signing users
into your app — it mints two further RS256 JWTs, signed with the same JWKS keys but with
deliberately different iss/aud values from the access token:
| Token | iss | aud | What it is |
|---|---|---|---|
| Access token | zynth-auth | zynth-services or a resource identifier | Credential for API calls — verified by resource servers, opaque to clients |
| id_token | The OIDC issuer URL (the discovery issuer — https://auth.zynthmedia.com on the managed service) | Your client_id | Proof of login for your app — carries sub, tid, amr, sid |
| logout_token | The OIDC issuer URL | Your client_id | Signed back-channel logout notification |
The issuers never overlap: zynth-auth appears only in access tokens, the issuer URL only
in id_tokens and logout_tokens. Validate each token type with its own configuration — a
shared verifier config always rejects one of them. Full guidance:
Verifying tokens.
JWKS
Zynth Auth publishes its public signing keys as a JWKS document:
GET /.well-known/jwks.json
{
"keys": [
{ "kty": "RSA", "use": "sig", "alg": "RS256", "kid": "zynth-2026-q3", "n": "…", "e": "AQAB" }
]
}
Your services fetch and cache this to verify tokens offline — no per-request call to
Zynth Auth. Match the token header's kid to the right key.
The document carries up to three keys, in this order:
| Slot | Role |
|---|---|
previous | Retiring — verifies only, never signs. Published until the last token it signed has expired. |
active | The key currently signing new tokens. |
next | Staged ahead of a rotation — published before it ever signs, so caches have already seen it. |
In steady state only the active key is published; previous and next appear around a
rotation. That is what keeps verification unbroken across a cutover: tokens signed before it
still verify against previous, and a verifier that refreshed early already trusts next.
Because the count varies, select by kid — don't assume a fixed number of keys, and don't
treat keys[0] as the active one (during a rotation it is the retiring key).
Next
- Verifying tokens — the verification recipe.