Skip to content

Managing sessions

View as Markdown

A login is the start of a session, not the end of the integration. Session API provides four endpoints for the rest of the ordinary application session lifecycle: /refresh, /introspect, /logout, and /revoke-all. This page is the single reference for all of them.

For application code, prefer the Session SDK package for your language. See the SDK overview before hand-writing these HTTP calls.

If you’re using the OIDC flow, see also /end-session in the OIDC guide — it has a related but narrower purpose.

EndpointAuthenticates withIdempotentScope of effect
POST /refreshThe refresh token itselfNo (rotates the refresh token — each token works exactly once)One session
GET /userinfoThe access token as BearerRead-onlyOne session’s current shared profile
POST /introspectThe access token itselfRead-onlyOne session
POST /logoutThe refresh token itselfYes (calling twice returns revoked: true both times)One session
POST /revoke-allClient-auth JWT (RS256)YesAll sessions for an account, scoped to the calling app

None of these endpoints require setting up new infrastructure — they reuse the keys you already have from your initial integration.

Exchange a refresh token for a fresh access token and a new refresh token. Refresh is strict rotation (OAuth 2.1 BCP §4.14.2): the presented signed version is consumed while the same logical ApplicationSession keeps its stable payload sid, stores a new jti, and increments payload rotationVersion. Store the new refreshToken and use it for the next refresh. Re-presenting a stale version is treated as compromise and terminally revokes that session. Near-simultaneous requests with the same version (for example, two browser tabs) are the exception: during the short grace window they adopt the exact winning version instead of advancing again or logging the user out. Reusing it after that window still triggers compromise, so always store and send the latest token.

Terminal window
curl -X POST https://session-api.sudomimus.com/refresh \
-H "Content-Type: application/json" \
-d '{ "refreshToken": "..." }'

Response:

{
"accessToken": "<JWT>",
"refreshToken": "<rotated JWT>",
"claims": {
"email": { "requirement": "REQUIRED", "state": "GRANTED" },
"firstName": { "requirement": "OPTIONAL", "state": "GRANTED" },
"lastName": { "requirement": "OFF", "state": "UNKNOWN" },
"staticAvatar": { "requirement": "SYNTHETIC_ONLY", "state": "UNKNOWN" },
"animatedAvatar": { "requirement": "OFF", "state": "UNKNOWN" }
}
}

Persist the rotated refreshToken, replacing the one you just used — the old one is now invalid. The claims block is the same per-claim view returned by /redeem — see the claims block for how to read it.

Auth: none — possession of the refresh token is the credential.

The new access token’s TTL is the one resolved on the original /redeem (or /direct-issue/*, or OIDC /token). It is not re-resolved on refresh.

/refresh is not only a success-or-revoke endpoint. If, since the last token was minted, a required claim has stopped being satisfied — the developer escalated a claim policy from optional to required, or the user revoked a grant — the refresh is rejected with ClaimConsentRequired rather than minting a token missing that required claim.

Recovery depends on the client, because /refresh itself cannot collect consent:

  • Native clients (Steam / AccessKey) recover by re-running the original direct-issue, which returns an Errand handoff for the user to grant consent.
  • Browser applications recover by sending the user through an ordinary interactive login again.

This is rare in practice — it only happens when a policy or grant changes mid-session — but build your refresh path to surface a re-authentication prompt rather than treating every refresh failure as a hard logout.

/introspect — is this token still valid?

Section titled “/introspect — is this token still valid?”

Ask Sudomimus about the current status of an access token. Use this when you want to invalidate sessions promptly across services — e.g. when a user clicks “log out everywhere” and you need other tabs or services to notice within a bounded time.

Terminal window
curl -X POST https://session-api.sudomimus.com/introspect \
-H "Content-Type: application/json" \
-d '{ "accessToken": "..." }'

Response:

{
"status": "active",
"recommendedRecheckSeconds": 600
}

status is one of:

  • "active" — the sid resolves to an ACTIVE, live-authority ApplicationSession.
  • "revoked" — the session is terminally revoked or one of its authority bindings is no longer current.
  • "expired" — the fixed ApplicationSession expiry has passed.
  • "not_found" — the token is invalid or its sid does not resolve to a matching session.

recommendedRecheckSeconds is how long Sudomimus suggests you may cache the result before re-introspecting. It is always 600 seconds today. Treat the access token’s own exp claim as the upper bound on its usable lifetime; introspection is for catching early revocation.

Auth: none — the access token is self-authenticating. Anyone holding the token can ask whether it is still valid; nothing else is required.

Local signature verification covers correctness; introspection covers freshness. A reasonable pattern:

  • Verify the access token’s signature and exp claim on every request (cheap, local).
  • Call /introspect opportunistically — once per N minutes per session, on a background job, or when a user-visible state change suggests it.

You do not need to introspect on every request. Doing so would defeat the point of having a signed token in the first place.

Terminally revoke the ApplicationSession identified by one genuine refresh-token version. Its access tokens stop being reported as active by /introspect, and every later /refresh for that sid fails.

Terminal window
curl -X POST https://session-api.sudomimus.com/logout \
-H "Content-Type: application/json" \
-d '{ "refreshToken": "..." }'

Response:

{ "revoked": true }
  • revoked: true — the session was revoked (or was already terminal; calling twice is fine).
  • revoked: false — the token is invalid or not found.

Auth: none — possession of the refresh token authorizes logging out that session (RFC 7009 style).

/revoke-all — revoke every session for an account

Section titled “/revoke-all — revoke every session for an account”

Advance the account/application revocation authority, then best-effort revoke the currently enumerated application-session rows. Every older session becomes unusable even if cleanup does not enumerate its row. Use this for account-takeover incident response, “log me out of all devices”, or support-initiated session termination.

The account is identified by its sector subject — payload sub on the application’s access token and the value you key users on. Refresh tokens carry no user identifier.

Terminal window
curl -X POST https://session-api.sudomimus.com/revoke-all \
-H "Content-Type: application/json" \
-H "Authorization: SudomimusClientJWT $SUDOMIMUS_CLIENT_AUTH_JWT" \
-d '{ "subject": "sub_9SQ5535CRWNDDM2T" }'

Response:

{ "revoked": true }

revoked: true acknowledges the request. Cleanup results are deliberately private because they are not the source of revocation authority. Unknown and out-of-sector subjects return the same acknowledgement so the endpoint does not reveal whether the subject exists.

Auth: a client-auth JWT with aud = "sudomimus-session". The scope of the action is implicit — only sessions issued to that account within the calling application are touched. You cannot use one application’s client-auth key to revoke sessions in another application.

Putting it together — a typical session lifecycle

Section titled “Putting it together — a typical session lifecycle”
/redeem ──► access (3h) refresh (30d)
│ │
│ │
▼ │
used on every request │
│ │
▼ expires │
/refresh ◄──────────────┤ new access + rotated refresh
│ │
▼ │
│ │
user clicks "log out" ──► /logout
revoked
/introspect ──► "revoked"

For the OIDC variant of refresh, see OIDC relying parties — Refresh.