Session Management¶
A session is one refresh-token family: every login starts a new family, and refresh rotation keeps that family alive. The sessions router surfaces those families so a user can see where they're signed in and sign out a specific device, without affecting the others.
What you get¶
| Method | Route | Description |
|---|---|---|
GET |
/auth/sessions |
List the user's active sessions. The device making the request is flagged current. |
DELETE |
/auth/sessions/{family_id} |
Revoke one session. 404 if it isn't the caller's. |
POST |
/auth/sessions/revoke-others |
Sign out everywhere except the current session. |
Each session in the list carries:
{
"family_id": "9f3cā¦",
"ip_address": "203.0.113.7",
"user_agent": "Mozilla/5.0 ā¦",
"created_at": "2026-06-11T09:00:00Z",
"last_used_at": "2026-06-11T11:42:00Z",
"expires_at": "2026-07-11T09:00:00Z",
"current": true
}
created_at is when the session began (the first login), last_used_at is the most recent refresh, and current marks the session whose access token made the request.
Enabling it¶
The router is opt-in like the others, and it mounts automatically for the bundled SQLAlchemy and SQLModel adapters ā no extra wiring. It registers under your auth prefix (default /api/v1/auth).
fullauth.init_app(app) # sessions router included by default
# or opt in explicitly alongside the routers you want
fullauth.init_app(app, include_routers=["auth", "profile", "sessions"])
To leave it off, omit "sessions" from include_routers.
How "current session" works¶
The access token carries a family_id claim identifying its session. When listing, the server compares each session's family against the caller's token and sets current on the match. revoke-others uses the same claim to decide which session to keep.
Access tokens issued before upgrading don't carry the claim; their current flag resolves on the next login or refresh.
Using a custom adapter¶
The built-in adapters implement session listing out of the box. A custom adapter opts in by inheriting SessionAdapterMixin and implementing three methods:
from fastapi_fullauth.adapters import AbstractUserAdapter, SessionAdapterMixin
from fastapi_fullauth.types import SessionInfo
class MyAdapter(AbstractUserAdapter, SessionAdapterMixin):
async def list_user_sessions(self, user_id) -> list[SessionInfo]: ...
async def revoke_user_session(self, user_id, family_id) -> bool: ...
async def revoke_user_sessions_except(self, user_id, keep_family_id) -> int: ...
If the adapter doesn't implement the mixin, the sessions router is skipped ā the same way admin is skipped without role support.
Migration¶
Recording device and IP adds two nullable columns to the refresh-token table: user_agent and ip_address. Both are nullable, so existing rows stay NULL and the change is additive. See Database Migrations for the workflow.