Tokens and verification
Sudomimus issues three kinds of token, signed by different keys and verified through different mechanisms. This page is the single reference for verification and token contents — what the claims mean and the rules that decide their values.
At a glance
Section titled “At a glance”| Token | Issued by | Signed with | Verified via | Carries |
|---|---|---|---|---|
| Access token | Connect (/redeem, /refresh, /direct-issue/*, /token) | The application’s token-signing private key | POST /info → applicationPublicKey | subject, firstName, lastName?, emailAddress? |
| Refresh token | Connect (/redeem, /direct-issue/*, /token) | The application’s token-signing private key | Same as access token | subject |
| OIDC ID token | OIDC (/token) | A platform-wide OIDC signing key | oidc.sudomimus.com/.well-known/jwks.json | sub, iss, aud, exp, iat, nonce?, auth_time?, email?, name? |
All three are JWTs signed with RS256 (RSA-2048).
Access and refresh tokens
Section titled “Access and refresh tokens”These are the tokens your application backend deals with day to day. Both are signed by Sudomimus using a keypair that is specific to your application — every application has its own pair, and rotating one application’s key has no effect on any other.
Verification
Section titled “Verification”Your application verifies signatures against its own token-signing public key, fetched from POST /info. The recommended flow is:
- On startup, call
POST /infowith yourapplicationAnchorand cache the returnedapplicationPublicKey. - For each incoming request, parse the JWT, check
kty === "Access"in the header (or"Refresh"for refresh tokens), checkexp, and verify the signature against the cached key. - After the application key is rotated (via the developer portal), clear the cache and refetch.
The official SDKs do all of this for you — see verifyAccessToken in the SDK reference.
There is no JWKS for Connect tokens. Verification is per-application by design: it means a relying party only needs to know one key (the one tied to its own applicationAnchor), and a compromise of one application’s key cannot affect any other.
The kty claim
Section titled “The kty claim”Sudomimus access and refresh JWTs carry a custom kty field in the JWT header to make the token type explicit:
- Access tokens:
kty: "Access"(exact case) - Refresh tokens:
kty: "Refresh"(exact case)
Reject any access-token check that finds kty: "Refresh" (or vice versa) — the official SDKs do this for you, but if you verify manually, this is one of the easier mistakes to miss.
TTL bounds
Section titled “TTL bounds”| Default | Minimum | Maximum | |
|---|---|---|---|
| Access token | 3 hours (10800s) | 60 seconds | 7 days (604800s) |
| Refresh token | 30 days (2592000s) | 1 day (86400s) | 365 days (31536000s) |
Per-rule and per-inquiry overrides are subject to these bounds. When multiple TTLs apply (e.g. one from a Layer 1 rule and another from a Layer 3 inquiry constraint), Sudomimus folds them by taking the minimum. The refresh TTL is then raised, if necessary, so that it is always at least as long as the access TTL.
Access token claims
Section titled “Access token claims”Sudomimus access and refresh tokens carry every standard JWT claim (kty, iss, aud, sub, iat, exp) in the JWT header, and only the application-specific claims (subject, firstName, lastName, emailAddress) in the body. If you verify by hand, read iss / aud / exp from the header, not the payload — the official SDKs do this for you.
// JWT header{ "alg": "RS256", "kty": "Access", "iss": "sudomimus.com", "aud": "<applicationAnchor>", "sub": "<refreshTokenIdentifier>", "iat": <epoch>, "exp": <epoch>}// JWT body{ "subject": "<sector subject>", "firstName": "<string>", "lastName": "<string, optional>", "emailAddress": "<string, optional>"}| Claim | Meaning |
|---|---|
kty | Always "Access". Reject any token where this differs. |
subject | The application-visible sector subject — a per-(account × sector) opaque identifier (e.g. sub_9SQ5535CRWNDDM2T). This is the value to key your users on. Stable for a given user within your sector, but rotatable by the user, and different across sectors. Treat it as opaque — do not parse it. The raw account UUID is never put in a token. |
firstName, lastName | The user’s display name. lastName is optional. |
emailAddress | Verified email associated with this login. Selection rule below. Omitted when the account owns no verified email (e.g. Steam-only or AccessKey-only accounts). |
iss | Always "sudomimus.com". |
aud | The applicationAnchor of the application this token was issued for. |
sub | The identifier of the refresh token this access token was minted from. Useful for revocation correlation; not a user identifier. |
iat, exp | Standard JWT issued-at / expiration, in seconds since epoch. |
Refresh token claims
Section titled “Refresh token claims”The same header/body split applies — standard claims in the header, the sector subject in the body.
// JWT header{ "alg": "RS256", "kty": "Refresh", "iss": "sudomimus.com", "aud": "<applicationAnchor>", "iat": <epoch>, "exp": <epoch>}// JWT body{ "subject": "<sector subject>"}The subject here is the same sector subject carried on the access token. It is informational only — Sudomimus identifies a refresh token by its internal handle, not by reading this body — so applications should not key revocation or storage off it.
The refresh token body deliberately omits display name and email — those are looked up fresh from the account row each time a new access token is minted, so the values in your access tokens stay current as the user updates their profile.
How emailAddress is selected
Section titled “How emailAddress is selected”The emailAddress claim on access tokens is chosen at issuance time by a single deterministic rule:
- If the user logged in with email-OTP, the claim is exactly the email they typed (and proved they own by entering the correct verification code).
- Otherwise — passkey, Steam ticket, AccessKey, or any other login method — the claim is the account’s primary email from the
EmailIdentitytable. - If the account owns no verified email at all (e.g. a Steam-only account with no linked address), the claim is omitted entirely. Your verification code should treat
emailAddressas optional.
The “primary email” defaults to the first verified email the account acquired, and the user can change it from Sign-in methods in the With portal. Once an access token has been minted, refreshing it (via POST /refresh) preserves the original emailAddress value, so applications can safely cache it across refreshes within a single session.
OIDC ID tokens
Section titled “OIDC ID tokens”If your application is integrated as an OIDC relying party, the /token endpoint additionally returns an id_token alongside access_token. The ID token is signed by a platform-wide OIDC signing key (not your per-application key) and is verified against the JWKS at https://oidc.sudomimus.com/.well-known/jwks.json.
ID token claims follow the OpenID Connect standard:
{ "iss": "https://oidc.sudomimus.com", "sub": "<sector subject>", "aud": "<client_id>", "exp": <epoch>, "iat": <epoch>, "nonce": "<from /authorize, if provided>", "auth_time": <epoch, if applicable>, "email": "<if 'email' scope granted>", "name": "<if 'profile' scope granted>"}| Claim | Meaning |
|---|---|
iss | Always "https://oidc.sudomimus.com". |
sub | The sector subject — the same per-(account × sector) value carried as subject on the paired access token, not the raw account UUID. Stable for a user within your sector, rotatable, and different across sectors. |
aud | The OIDC client_id of the relying party. |
exp, iat | Standard JWT lifetimes, in seconds since epoch. |
nonce | Echoed from the relying party’s /authorize request on initial issuance. Not echoed on refresh-token grants, per OIDC core 1.0 §12.1. |
auth_time | When the user actually authenticated, in seconds since epoch. Preserved across refresh-token grants. |
email | The user’s email — same value as on the paired access token. Only present when the email scope was granted. |
name | The user’s full name, constructed as firstName lastName. Only present when the profile scope was granted. |
OIDC signing keys are rotated periodically; the JWKS always publishes the keys for both the currently-active and the recently-retired key so verification continues to work during rotation.
OIDC access tokens
Section titled “OIDC access tokens”OIDC /token also returns an access_token whose shape is identical to the Connect access token described above — same kty, same per-application signing key, same claims. The difference is only in the issuance path; once issued, your application verifies it exactly the same way (POST /info → cached public key).
See OIDC relying parties for the full RP flow, including /userinfo and /end-session.
When to use which
Section titled “When to use which”- Connect protocol (access / refresh tokens via
POST /info): your own application backend talks to Sudomimus directly. Lowest overhead, no extra hop. - OIDC (ID tokens via JWKS): your application uses an off-the-shelf OIDC library, or you want to integrate with a third party that already speaks OIDC. Sudomimus acts as the IdP.
You generally pick one or the other per application — there is no requirement to use both.