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 against the OP browser session.

SenseCrypt keeps a short-lived OP browser session (the sc_op_session cookie, 8 hours by default and configurable per tenant) so a second application can be authorized without another face ceremony and prompt=none can answer silently. Everything longer-lived than that is about tokens. This guide covers refresh tokens, rotation, revocation, and logout — and how logout ends both.

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)

"Logout" means two things at once: ending the OP browser session behind the sc_op_session cookie (so the next /authorize runs a fresh face ceremony instead of answering silently) and revoking the refresh-token families the session left behind. Use the logout endpoint:

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

Key requirements:

  • Send an id_token_hint. It is what proves which application is asking, and it is the only route to a post_logout_redirect_uri redirect. Without one, SenseCrypt shows a confirmation page and — once the user confirms — signs them out and lands on its own signed-out page, never on your redirect. A request carrying a post_logout_redirect_uri but neither an id_token_hint nor a client_id is refused with a 400 page and nothing is signed out. The hint is verified for signature and issuer, but an expired hint is accepted.
  • Logout revokes that user's live refresh-token families for every application the ended browser session signed in to, plus the application the id_token_hint proves — not just the (subject, client) pair that asked. Signing out of one application therefore signs the user out of the others that shared the session.
  • 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 an email change sever the identity plane: the user's device keys, passkeys, refresh-token families and OP browser sessions all go at once.
  • Group-member removal is an authorization change, so it stays on the session planes — the device key and passkey survive, because the user may still reach other applications through other groups, and the access gate is what closes the application they lost. It revokes the refresh-token families for the applications the user can no longer reach, and each of those applications that opted into the access-revoked initiator receives a back-channel logout token for every live browser session of theirs that had signed in to it (with no live session there is nothing to announce). A user left in no app-attached group also loses every remaining refresh-token family in the tenant and their OP browser session, and every application that session had signed in to which opted in is told.
  • 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