Skip to content

Managing sessions

View as Markdown

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

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
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 refresh token you present is consumed and invalidated in the same call, and the response hands you its replacement. Store the new refreshToken and use it for the next refresh. Re-presenting an already-used refresh token is treated as family compromise — the entire refresh-token family is revoked and the user must re-authenticate. Near-simultaneous concurrent refreshes of the same token (e.g. two browser tabs at once) are the exception: they converge on a single new session rather than logging the user out. Reusing a token after its replacement has been issued still triggers compromise, so always store and send the latest rotated token regardless.

Terminal window
curl -X POST https://connect-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" }
}
}

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://connect-api.sudomimus.com/introspect \
-H "Content-Type: application/json" \
-d '{ "accessToken": "..." }'

Response:

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

status is one of:

  • "active" — the underlying refresh token exists, is not suspended, and is not expired.
  • "revoked" — the underlying refresh token has been explicitly suspended (via /logout or /revoke-all).
  • "expired" — the underlying refresh token is past its expiredAt date.
  • "not_found" — the token does not match any record under the calling application, or the token cannot be parsed.

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.

Suspend one refresh token. The token’s access tokens stop being reported as active by /introspect, and /refresh against it will fail.

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

Response:

{ "revoked": true }
  • revoked: true — the token was suspended (or was already suspended/expired; 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 — kill every session for an account

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

Suspend every refresh token issued to a specific account, within the calling application. 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 — the subject value your application sees on its tokens (the sub you key your users on), not the raw account UUID. Sudomimus reverse-maps it to the underlying account internally.

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

Response:

{ "revokedCount": 3 }

revokedCount is the number of refresh tokens that were suspended by this call. Already-suspended or already-expired tokens are not double-counted.

Auth: a client-auth JWT, signed exactly like the one for /establish. 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.