Sessions
ES256 access tokens, refresh-token rotation with theft detection, per-account lockout, session list/revoke, and CAEP/SSF-shaped real-time revocation signals.
Authentication issues a token pair: a short-lived ES256 access JWT and a rotating refresh token, both tied to a server-side session you can list and revoke. This page covers the token model, rotation + theft detection, lockout, and session management.
The token pair
{
"access_token": "eyJhbGciOiJFUzI1Ni…",
"token_type": "Bearer",
"expires_at": "2026-06-04T12:15:00Z",
"refresh_token": "…",
"session_id": "…",
"user_id": "…",
"tenant_id": "…"
}- Access token — an ES256 JWT. Verify it offline against the public JWKS
(
/.well-known/jwks.json): signature, expiry, issuer, audience. No per-request network call. - Refresh token — opaque; exchange it for a fresh pair at
/v1/auth/refresh.
const claims = await qeetid.sessions.verify(accessToken);
// claims.userId, claims.tenantId, claims.sessionIdWhy ES256 + JWKS
Tokens are signed with ECDSA P-256 (asymmetric). The JWKS is public, so any
service verifies tokens without a shared secret. Signing keys carry an RFC
7638
kid, retire on a grace window, and are guarded against
alg-confusion. The SDKs cache the JWKS and refetch on an unknown
kid.
Refresh-token rotation + theft detection
Each refresh rotates the token: the old refresh token is invalidated and a new one issued. If a previously-rotated (i.e. stolen and replayed) refresh token is ever presented, Qeet ID revokes the session and audits the event — so a leaked token can't be used silently. Refresh also re-checks the account itself, not just the session: a suspended or deleted user's still-valid refresh token is rejected too, even though a plain status change doesn't touch the session row directly.
/v1/auth/refreshRotate the token paircurl -X POST https://api.id.qeet.in/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refresh_token":"…"}'Next.js does this for you
@qeetid/nextjs middleware refreshes a near-expiry session
silently — it calls the token endpoint, persists the rotated refresh token in
the encrypted cookie, and re-runs the request — so users aren't bounced to
login.
Account lockout
On top of distributed IP rate limiting, Qeet ID applies a
per-account failed-attempt throttle and temporary lockout to blunt credential
stuffing. Locked accounts return 429 on login until the window clears. This is on by
default.
List & revoke sessions
For an account-security screen, list the user's active sessions and let them revoke
any one. logout revokes the caller's current session.
/v1/auth/sessionsList the caller's sessions/v1/auth/sessions/{id}Revoke a session/v1/auth/logoutRevoke the current sessionconst sessions = await qeetid.sessions.list();
await qeetid.sessions.revoke(sessionId);Password reset revokes all of a user's sessions (see Sign-up & passwords).
Real-time revocation signals (CAEP/SSF-shaped)
Access tokens are stateless JWTs — verified by signature alone, with no per-request database check — so revoking a session doesn't retroactively invalidate a token that's already out in the wild; it just stops being renewable at the next refresh. Qeet ID narrows that window three ways, deliberately short of full OpenID CAEP/SSF protocol interop (no Shared Signals Framework stream, no SET tokens, no receiver registry):
- A 10-minute access-token TTL bounds the exposure window by default.
- Refresh re-checks the account, not just the session (see above) — closing the gap where a suspended/deleted account's token kept working until it happened to expire.
- Two signals ride the existing webhook dispatcher, so a subscribed tenant finds out in seconds instead of waiting out the TTL:
| Event | Fires on |
|---|---|
session.revoked | Logout, an explicit session revoke, or refresh-token-reuse theft detection |
token.claims_change | A direct role grant or revoke (Authorization) |
{
"user_id": "…",
"session_id": "…",
"reason": "reuse_detected",
"revoked_at": "…"
}Opt-in via the webhook subscription itself
No new settings screen — subscribing to these is the same events filter
every webhook subscription already uses. Add session.revoked and/or
token.claims_change to a subscription's events list (or leave it empty,
which subscribes to everything) at webhook
creation.
Who am I
/v1/auth/me returns the caller's current principal context (user, tenant,
session) — handy for hydrating an app shell.
/v1/auth/meCurrent principal contextAlways verify, never trust
Authorization decisions must be based on a verified access token (or a server-side session lookup), never on cookie presence alone. Verify the JWT signature and expiry on every protected request.
MFA & step-up
TOTP + recovery codes, email/SMS OTP factors, WebAuthn as a second factor, step-up MFA, and an adaptive risk engine (bot-score, impossible travel, device reputation).
OIDC / OAuth provider
Qeet ID is a full OpenID Connect provider — discovery, JWKS, dynamic client registration, Authorization Code + PKCE, hosted login & consent, ES256 ID tokens, device grant, and M2M.