Sessions
Overview
The sessions module provides user session management, allowing users to view their active sessions (with IP address, user agent, and expiry information) and revoke individual sessions or all other sessions. Sessions are created during OAuth login and are tied to refresh tokens via a SHA-256 token hash.
Architecture
ID App (NextAuth) ──> OAuth Login
|
v
exchangeToken mutation
|
+--> create_session (DB)
+--> cache_session (Redis)
+--> Issue JWT with session_id claim
|
v
Dashboard UI
|
v
GraphQL (SessionQuery / SessionMutation)
|
v
db::auth (PostgreSQL sessions table)
Session Lifecycle
- Creation -- During
exchangeToken, a session row is created with the user ID, active account ID, SHA-256 hash of the refresh token'sjti, client IP, user agent, and expiry timestamp. The session is also cached in Redis for fast lookup. - JWT Embedding -- The session ID is embedded in the JWT via
create_token_with_session, allowing the API to identify which session corresponds to the current request. - Refresh -- During
refreshToken, the session is looked up by token hash. A new JWT is issued with the same session ID. The refresh token itself is not rotated. - Logout -- The
logoutmutation deletes the session from both PostgreSQL and Redis by token hash. - Revocation -- Users can delete specific sessions or all sessions except the current one.
Session Filtering
Only non-expired sessions are returned by list_sessions_for_user (filtered by expires_at > now()).
API
GraphQL Queries
| Query | Args | Returns | Permission |
|---|---|---|---|
sessions | -- | [Session] | sessions:read |
Returns all active (non-expired) sessions for the current user, ordered by created_at DESC.
GraphQL Mutations
| Mutation | Args | Returns | Permission |
|---|---|---|---|
deleteSession | id: UUID | Boolean | sessions:delete |
deleteAllOtherSessions | -- | Boolean | sessions:delete |
deleteSessionverifies the session belongs to the current user before deletion.deleteAllOtherSessionsuses thesession_idfrom the JWT to identify the current session and deletes all others for the user.
GraphQL Types
type Session {
id: UUID!
userId: UUID!
activeAccountId: UUID
ipAddress: String
userAgent: String
expiresAt: String!
createdAt: String!
}
Note: The token_hash field is present in the database row but is not exposed via GraphQL.
REST Endpoints
Sessions are user-scoped; the REST handlers enforce ownership in code rather than via an RBAC permission string. All paths live under /v1/users/me/sessions.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /v1/users/me/sessions | Auth | List active (non-expired) sessions for the current user |
DELETE | /v1/users/me/sessions/{id} | Auth | Revoke one session (must belong to the current user) |
DELETE | /v1/users/me/sessions | Auth | Revoke all other sessions (keeps the current one) |
Permissions
| Permission | Description |
|---|---|
sessions:read | View active sessions (used by the GraphQL sessions query) |
sessions:delete | Revoke sessions (used by the GraphQL deleteSession / deleteAllOtherSessions mutations) |
Included in: Owner, Administrator, Moderator, Viewer roles.
Database
Table: sessions
| Column | Type | Description |
|---|---|---|
id | UUID (PK) | Session ID |
user_id | UUID (FK) | Session owner |
active_account_id | UUID (FK) | Currently selected account |
token_hash | TEXT | SHA-256 hash of the refresh token's jti |
ip_address | INET | Client IP address (stored as inet, returned via host()) |
user_agent | TEXT | Client user agent string |
expires_at | TIMESTAMPTZ | Session expiry |
created_at | TIMESTAMPTZ | Creation timestamp |
DB Functions
| Function | Description |
|---|---|
create_session | Insert a new session with token hash, IP, user agent, and expiry |
list_sessions_for_user | List non-expired sessions ordered by created_at DESC |
find_session_by_token_hash | Lookup by token hash (used during refresh) |
delete_session_by_id | Delete a specific session with user ownership check |
delete_other_sessions | Delete all sessions except the specified one |
delete_session | Delete by token hash (used during logout) |
delete_all_sessions_for_user | Delete all sessions (used for account dissolution) |
Key Files
| File | Purpose |
|---|---|
apps/api/src/graphql/sessions.rs | GraphQL queries and mutations |
apps/api/src/graphql/auth.rs | Token exchange and refresh (session creation) |
apps/api/src/db/auth.rs | Session CRUD and token hash operations |
crates/lo-auth/src/jwt.rs | JWT creation with embedded session ID |
crates/lo-auth/src/api_key.rs | hash_token function (SHA-256 hashing) |