ASVS 5.0 V7.5.2 (Level 2) requires that users can view their currently active sessions and terminate any or all of them, having authenticated again with at least one factor before doing so.
Why it matters
Users spot problems you cannot. A login from a city they have never visited is obvious to them. Without a session list, they have no way to react except changing the password and hoping.
How to implement it
Store a little metadata per session at creation: created time, last seen, coarse location or IP, and a parsed device label. Show it on an account page and mark the current session.
@app.get("/account/sessions")
def list_sessions(user=Depends(current_user)):
rows = db.query(
"SELECT id, created_at, last_seen, ip_hint, device FROM sessions "
"WHERE user_id = ? AND revoked = 0 ORDER BY last_seen DESC", user.id)
return [{**r, "current": r["id"] == user.session_id} for r in rows]
@app.post("/account/sessions/{sid}/revoke")
def revoke(sid: str, user=Depends(recently_reauthenticated)):
db.execute("UPDATE sessions SET revoked = 1 WHERE id = ? AND user_id = ?", sid, user.id)
return {"ok": True}
The user id check in the update statement matters: without it, a user could revoke someone else's session by guessing an id. Use random public ids for the list, not the secret token. The reauthentication dependency enforces the "authenticate again" part of the requirement.
How to test it
- Sign in from two browsers and confirm both appear in the list with sensible labels.
- Try to revoke without recent reauthentication and expect a challenge.
- Revoke one session and confirm that browser is rejected next request.
- As user A, try to revoke a session id belonging to user B and expect a failure.
Common mistakes
- Exposing the real session token in the page or API.
- Skipping the ownership check on revoke.
- Showing stale lists because last seen is never updated.
Quick check
Why require reauthentication to end sessions?
An attacker with a stolen session could otherwise end the real user's sessions and lock them out of their own account.