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) 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 (/token) A platform-wide OIDC signing key oidc.sudomimus.com/.well-known/jwks.json sub, iss, aud, exp, iat, at_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. 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 access TTL is then reduced, if necessary, so that it never exceeds the refresh TTL.

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 verifies the signature, strongly loads the ApplicationSession by sid, and exact-matches the application, jti, and version before it rotates anything. The internal session separately proves the current subject authority.

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.

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>,
"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 Binds the ID token to the paired access token.
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.

OIDC /token returns 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.

  • Connect protocol plus Session API (access / refresh tokens via the per-application Session JWKS): 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.