Skip to content

Tokens and verification

View as Markdown

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.

Token Issued by Signed with Verified via Carries
Access token Connect, Session API, Native API, or OIDC (/redeem, /refresh, /direct-issue/*, /token, /authorize) The application’s active token-signing private key Session GET /applications/{applicationAnchor}/jwks.json by kid Payload iss, aud, sub, sid, jti, iat, exp; Workload tokens also carry act.sub
Refresh token Connect, Session API, Native API, or OIDC (/redeem, /refresh, /direct-issue/*, /token) The application’s active token-signing private key Same as access token Payload iss, aud, sid, jti, iat, exp, rotationVersion
OIDC ID token OIDC (/authorize, /token) A platform-wide OIDC signing key oidc.sudomimus.com/.well-known/jwks.json sub, iss, aud, exp, iat, at_hash?, c_hash?, nonce?, auth_time?, amr?, acr?

All three are JWTs signed with RS256 (RSA-2048).

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.

Your application verifies signatures against its own token-signing JWK Set, published by Session API. The recommended flow is:

flowchart TD
    Receive["Receive a JWT"] --> Parse["Parse as untrusted data"]
    Parse --> Header{"Expected alg, typ, aud,<br/>non-empty kid, and valid exp?"}
    Header -->|No| Reject["Reject"]
    Header -->|Yes| Cached{"kid found in the cached<br/>application JWK Set?"}
    Cached -->|Yes| Verify["Verify the signature"]
    Cached -->|No| Refresh["Refresh the configured<br/>application JWK Set once"]
    Refresh --> Found{"kid found now?"}
    Found -->|No| Reject
    Found -->|Yes| Verify
    Verify --> Valid{"Signature valid?"}
    Valid -->|No| Reject
    Valid -->|Yes| Trust["Trust the verified claims"]
  1. Fetch GET https://session-api.sudomimus.com/applications/{applicationAnchor}/jwks.json and cache the response according to its Cache-Control header.
  2. For each incoming request, parse the JWT as untrusted data. Require alg === "RS256", the expected typ, aud === applicationAnchor, a non-empty kid, and a valid exp.
  3. Select the exact JWK whose kid matches the header and verify the signature. Only after verification may your application trust any claim.
  4. If the kid is unknown, refresh the JWK Set once immediately before rejecting. Do not derive a fetch URL from an untrusted audience; use your configured application anchor and Session API origin.

The JWK Set may contain a prepublished next key, the active signer, and retiring keys that still verify outstanding tokens. Revoked and retention-expired keys are omitted. This overlap is what lets key rotation preserve verification for tokens already in circulation.

Emergency revocation removes a retiring key from fresh Session JWKS responses immediately. A verifier that already cached that public key may continue accepting its signatures for the remainder of the advertised cache lifetime, up to 300 seconds after its last successful fetch. This bounded convergence is part of offline JWT verification; do not interpret revocation as instantaneous at every cache-honoring verifier. Use Session POST /introspect in addition to signature verification when a sensitive operation requires current live session authority.

Application-token JWKS is intentionally per application, separate from the platform-wide OIDC JWKS. A verifier only receives keys belonging to its configured applicationAnchor, and rotating one application has no effect on another.

Sudomimus access and refresh JWTs carry an explicit typ protected-header field:

  • Account access tokens: typ: "vnd.sudomimus.application-access+jwt"
  • Agent and Automation (Workload) access tokens: typ: "vnd.sudomimus.workload-access+jwt"
  • Refresh tokens: typ: "vnd.sudomimus.application-refresh+jwt"

Reject a token whose typ does not match the credential your endpoint expects. An Account-only endpoint must reject Workload tokens even when their signatures are valid. To accept Agents or Automations, the application must both enable the exact authentication method and implement Workload token verification. Enabling an authentication method alone does not make an Account-only verifier accept actors.

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. See Token TTLs in the rule model for how matching values are folded across layers and constraints.

The protected header is deliberately small. Registered JWT claims and session bindings live in the payload. sub is the pairwise sector subject and the user key your application should use; sid is the stable logical ApplicationSession id; jti identifies one bearer instance. No application token contains profile data or a raw account id.

// JWT header
{
"alg": "RS256",
"kid": "<signing-key identifier>",
"typ": "vnd.sudomimus.application-access+jwt"
}
// JWT payload
{
"iss": "https://sudomimus.com",
"aud": "<applicationAnchor>",
"sub": "<sector subject>",
"sid": "<application session identifier>",
"jti": "<access token identifier>",
"iat": <epoch>,
"exp": <epoch>
}
Claim Meaning
typ (header) "vnd.sudomimus.application-access+jwt" for Account access; "vnd.sudomimus.workload-access+jwt" for Agent or Automation access. Accept only the types your endpoint supports.
act.sub Present only in Workload access tokens: the Agent or Automation’s pairwise sector subject. act contains exactly sub; Account tokens must omit act.
sub 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.
sid The stable identifier of the logical ApplicationSession. Access and refresh tokens issued for one login share it across refresh rotations. It is not a user id.
jti The unique identifier of this access-token instance. It is distinct from sid and changes whenever a new access token is issued.
iss Sudomimus’s HTTPS application-token issuer.
aud The applicationAnchor of the application this token was issued for.
iat, exp Standard JWT issued-at / expiration, in seconds since epoch.

The access-token example above is an Account token. A Workload token uses the Workload typ and adds "act": { "sub": "<workload sector subject>" } to the payload. Its top-level sub still identifies the owner Account, while act.sub identifies the acting Agent or Automation within the sector. Validate the exact actor shape and keep both identities when making application authorization decisions. Neither identifier is a raw Account or Workload UUID. Authentication and credential scope do not grant business permissions inside your application.

Workload sessions use the same refresh-token type as Account sessions. Refresh tokens contain neither sub nor act; refreshing resolves the session’s current authority and returns the corresponding Account or Workload access-token type.

The refresh token uses the same minimal JOSE header. Its payload deliberately omits sub and all profile fields; sid, jti, and rotationVersion identify one exact signed refresh version.

// JWT header
{
"alg": "RS256",
"kid": "<signing-key identifier>",
"typ": "vnd.sudomimus.application-refresh+jwt"
}
// JWT payload
{
"iss": "https://sudomimus.com",
"aud": "<applicationAnchor>",
"sid": "<application session identifier>",
"jti": "<refresh token version identifier>",
"iat": <epoch>,
"exp": <epoch>,
"rotationVersion": 1
}

rotationVersion is a positive integer that increases by one on each successful rotation. Store the newly returned refresh JWT as one opaque credential; do not edit or derive a token from sid, jti, or the version. Sudomimus accepts refresh only when the token is valid for the application and the session remains active. See Managing sessions for rotation, concurrent refresh, and token reuse behavior.

Use GET https://session-api.sudomimus.com/userinfo with Authorization: Bearer <accessToken> to fetch current consent-gated profile values. The response always carries sub and may carry email, email_verified, name, given_name, family_name, picture, and the private claim picture_animated. Treat these fields as live, replaceable profile data; key users only by payload sub.

Use GET https://session-api.sudomimus.com/claim-state with the same Bearer token when you need the live policy requirement and consent state without the profile values. Its claim keys are email, given_name, family_name, picture, and picture_animated.

OIDC can return an ID Token from /authorize or /token, according to the selected response type and granted scopes. Verify it against https://oidc.sudomimus.com/.well-known/jwks.json. It uses the platform-wide OIDC signing key.

The following example shows a token-endpoint ID Token. Its claims follow the OpenID Connect standard:

{
"iss": "https://oidc.sudomimus.com",
"sub": "<sector subject>",
"aud": "<client_id>",
"exp": <epoch>,
"iat": <epoch>,
"at_hash": "<access-token hash>",
"nonce": "<from /authorize, if provided>",
"auth_time": <epoch, if applicable>,
"amr": ["<authentication method>"],
"acr": "<authentication context>"
}
Claim Meaning
iss Always "https://oidc.sudomimus.com".
sub The sector subject — the same per-(account × sector) value carried as payload sub on the paired access token. 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.
at_hash Present when the ID Token accompanies an access token, including /token responses. Validate it against that access token.
c_hash Present when an authorization response includes both a code and an ID Token. Validate it against that code. Omitted from /token ID Tokens.
amr, acr Authentication method references and authentication context.

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.

For authorization responses, id_token carries neither hash; id_token token carries at_hash; code id_token carries c_hash; and code id_token token carries both. Front-channel ID Tokens require a request nonce. Code-only requests may omit it.

Only response_type=id_token includes scope-requested profile claims allowed by current policy and grant. Other ID Tokens omit profile data. Issued JWTs are snapshots; revocation cannot erase data already delivered.

OIDC /authorize and /token return the same minimal per-application access token described above. It contains no scope-dependent profile fields. Your application verifies it against the per-application Session JWKS and fetches scoped identity claims from the discovered OIDC /userinfo endpoint.

See OIDC relying parties for the full RP flow, including /userinfo and /end-session.

Use Choose an integration path for the current comparison of Connect, OIDC, device authorization, and native direct-issue. This page owns the token and verification contract shared by those paths.

For client signing keys and advanced OIDC responses, see RP keys and rotation and Advanced OIDC and capabilities.