SenseCrypt Docs
Guides

Refresh tokens and sessions

Keep users signed in with SenseCrypt refresh tokens — requesting offline_access, handling rotation and reuse detection, revoking sessions, and implementing logout without a server-side SSO cookie.

SenseCrypt has no server-side SSO cookie — every sign-in is a fresh, on-device face ceremony. "Keeping a user signed in" and "logging them out" are therefore about tokens, not a session cookie on the IdP. This guide covers refresh tokens, rotation, revocation, and logout.

Request a refresh token

A refresh token is issued only when you request the offline_access scope at the authorization step:

GET /v1/idp/oidc/authorize?client_id=...&scope=openid%20offline_access%20profile&...

If you omit offline_access, no refresh token comes back — the access token stands alone until it expires. Refresh tokens are opaque (not JWTs); treat them as bearer secrets and store them securely.

Refresh an access token

Exchange the refresh token at the token endpoint:

POST /v1/idp/oidc/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=<token>&client_id=<id>

(Confidential clients also present their client credential per their registered auth method.) You get a new access token — and, when rotation is enabled, a new refresh token you must store in place of the old one.

Rotation and reuse detection

Depending on the app's configuration — and always for SPA (public) clients — each refresh rotates the token and returns a successor. This is a security feature, but it has rules you must respect:

  • Always store the newest refresh token returned and discard the previous one.
  • Reusing an already-rotated token outside a short overlap window is treated as a breach: SenseCrypt revokes the entire token family and returns invalid_grant. A stolen-and-replayed token thus takes out the legitimate session too — the safe failure.
  • Avoid racing refreshes. Two concurrent requests using the same refresh token can trip reuse detection. Serialize refreshes in your client, or rely on the short overlap window.

The access gate re-runs on every refresh

A refresh is not a free pass — it re-runs the full access gate and recomputes permissions:

  • A user who has been suspended, deleted, un-enrolled, or removed from the app's groups fails closed on their next refresh.
  • The permissions claim is recomputed fresh on every refresh, so a revoked role or a removed group membership shrinks the next access token — you don't wait for expiry.

Absolute lifetime

Every token family has an absolute lifetime fixed at issuance that rotation can never extend. When it lapses, the user must sign in again (a fresh face ceremony). Plan your UX around a periodic re-authentication rather than indefinite sessions.

Revoke a session (RFC 7009)

To end a session server-side, revoke a token at the revocation endpoint:

POST /v1/idp/oidc/revoke
Content-Type: application/x-www-form-urlencoded

token=<refresh_or_access_token>&client_id=<id>
  • Revoking a token revokes the refresh-token family it belongs to.
  • If the presented token parses as one of the client's access tokens, its jti is denylisted until it expires.
  • The endpoint always returns an empty 200, whether or not the token was found — there is no validity oracle. (Client authentication still applies: a bad client credential returns invalid_client.)

Log out (RP-Initiated Logout)

Because there's no OP-side SSO cookie, "logout" means revoking the subject's tokens. Use the logout endpoint:

GET /v1/idp/oidc/logout?id_token_hint=<id_token>&post_logout_redirect_uri=<url>&state=<opaque>

Key requirements:

  • id_token_hint is required. It's the only way the IdP knows which subject to act on — a missing hint is invalid_request (not a silent success that falsely claims the user signed out). The hint is verified for signature and issuer, but an expired hint is accepted.
  • Logout revokes every live refresh token for that (subject, client).
  • Any post_logout_redirect_uri must be on the client's dedicated post-logout allow-list — which is separate from the /authorize callback allow-list. Otherwise it's rejected (never an open redirect). state is round-tripped onto the redirect.
  • With no redirect URI, a static "You have been signed out" page is returned.

A note on access-token validity

Access tokens are self-contained JWTs — they remain valid until exp unless explicitly revoked (their jti denylisted, or their family revoked). Two implications:

  • Keep access-token lifetimes short and refresh as needed, so that suspensions and permission changes take effect promptly.
  • Your resource servers get immediate revocation only if they check the denylist (via the introspection endpoint) or if the access token expires soon. Local JWT verification alone will honor a token until exp.

Eager revocation on lifecycle events

You rarely need to revoke tokens by hand for lifecycle changes — SenseCrypt does it eagerly:

  • SCIM active: false / delete, account self-delete, and group-member removal all immediately revoke the affected user's device keys and refresh-token families.
  • Outstanding access tokens then read as inactive through introspection.

So removing a user from a group, or suspending them, ends their ability to refresh right away — you don't have to call the revocation endpoint yourself.

On this page