Active Sessions & Remote Revoke
Every successful SyAuth login (password, magic-link, or federated) now creates an ExtsUserSession row keyed on the refresh-token jti. End users can list every device signed into their account and revoke any of them on demand — the underlying tokens are pushed to the persistent TokenDenylist, so revocation survives Redis restarts.
End-user UI
A Active Sessions page is included in the SyAuth dashboard (/security/sessions). It shows:
- Device name (if known) and user agent
- IP address and last-seen timestamp
- Whether the session is currently active, revoked, or is the session making the call ("This device")
- Per-session Revoke button
- A Sign out of all other sessions button — revokes everything except the current session
API reference
All endpoints require Authorization: Bearer <access_token>. Every revoke also emits an immutable audit_event (session_revoke / session_revoke_all).
GET /user/sessions/
List every session (active + recently revoked) for the authenticated user.
{
"results": [
{
"id": "6f...",
"device_name": "",
"user_agent": "Mozilla/5.0 ...",
"ip_address": "203.0.113.42",
"created_at": "2026-04-17T10:00:00Z",
"last_seen_at": "2026-04-17T11:00:00Z",
"expires_at": "2026-04-24T10:00:00Z",
"revoked_at": null,
"revoked_reason": "",
"is_active": true,
"is_current": true
}
]
}
DELETE /user/sessions/{session_id}/
Revoke a specific session. Both the refresh-token jti and the captured access-token jti are added to TokenDenylist. Returns 204 No Content.
DELETE /user/sessions/
Revoke every active session for the user except the one making the call ("sign out of all other devices").
{ "revoked": 4 }
How revocation works
- On successful login, SyAuth creates an
ExtsUserSession(user, refresh_jti, access_jti, …). - When the session is revoked:
ExtsUserSession.revoked_atis set.- Both jti values are inserted into
TokenDenylistwith reasonuser_logout_all.
- On every authenticated request, SyAuth's access-token middleware already consults
TokenDenylist. A revoked jti means the token is rejected immediately — even if the JWT itself is still syntactically valid.
Because the denylist is persistent, revocations survive Redis restarts; because it's keyed on jti, it works cleanly across all our existing JWT flows.
Audit trail
Every revocation is logged to the Audit Log:
| Event | When |
|---|---|
session_create | Any time a new session row is created (login + magic-link + MFA verification). |
session_revoke | A single session is revoked from the UI or API. |
session_revoke_all | "Sign out of all other sessions" is used; metadata.revoked_count records the total. |
Operational notes
- New devices land without a name.
device_nameis a client-supplied label; populate it from your own UX if you want "MacBook Pro" rather than a blank cell. - Expired sessions are not auto-purged. They stay in the ledger for auditability. Add a nightly cleanup job if you want to trim.
- OAuth-client context is retained. If the session was initiated through a specific OAuth client, that's stored on the row so developers can answer "who signed in from which app?" later.
- Mobile apps should call
DELETE /user/sessions/on password change. We do not force this server-side yet — wire it into your client to match best practice.