Listing and Revoking Sessions
The session management UI
Users need to see their active sessions and revoke ones they do not recognize. This is standard in GitHub, Google, and most security-conscious apps.
The routes
// src/routes/sessions-mgmt.ts
import { route, group } from "@hectoday/http";
import { authenticate } from "../auth.js";
import {
getDeviceSessions,
deleteDeviceSession,
deleteAllDeviceSessions,
} from "../device-sessions.js";
import { deleteSession } from "../sessions.js";
import { getSessionId } from "../cookies.js";
export const sessionMgmtRoutes = group([
// List all active sessions
route.get("/me/sessions", {
resolve: (c) => {
const user = authenticate(c.request);
if (user instanceof Response) return user;
const currentSessionId = getSessionId(c.request);
const sessions = getDeviceSessions(user.id);
// Mark which session is the current one
const result = sessions.map((s: any) => ({
id: s.id,
deviceName: s.device_name,
ip: s.ip,
lastActiveAt: s.last_active_at,
createdAt: s.created_at,
isCurrent: s.id === currentSessionId,
}));
return Response.json(result);
},
}),
// Revoke a specific session
route.delete("/me/sessions/:sessionId", {
resolve: (c) => {
const user = authenticate(c.request);
if (user instanceof Response) return user;
const currentSessionId = getSessionId(c.request);
// Prevent revoking your own session via this endpoint
if (c.params.sessionId === currentSessionId) {
return Response.json(
{ error: "Use POST /logout to end your current session." },
{ status: 400 },
);
}
// Delete from both stores
const deleted = deleteDeviceSession(c.params.sessionId, user.id);
if (!deleted) {
return Response.json({ error: "Session not found" }, { status: 404 });
}
deleteSession(c.params.sessionId); // Remove from in-memory store
return Response.json({ message: "Session revoked." });
},
}),
// Sign out everywhere (except current session)
route.post("/me/sessions/revoke-all", {
resolve: (c) => {
const user = authenticate(c.request);
if (user instanceof Response) return user;
const currentSessionId = getSessionId(c.request);
// Delete all device sessions except the current one
deleteAllDeviceSessions(user.id, currentSessionId);
// Delete from in-memory store
// (In production, this would be a Redis or database operation)
const allSessions = getDeviceSessions(user.id);
for (const s of allSessions) {
if ((s as any).id !== currentSessionId) {
deleteSession((s as any).id);
}
}
return Response.json({ message: "All other sessions have been revoked." });
},
}),
]); What the user sees
# List sessions
curl -b cookies.txt http://localhost:3000/me/sessions
# [
# { "id": "abc", "deviceName": "Chrome on macOS", "ip": "1.2.3.4", "isCurrent": true, ... },
# { "id": "def", "deviceName": "Safari on iPhone", "ip": "5.6.7.8", "isCurrent": false, ... }
# ]
# Revoke the iPhone session
curl -b cookies.txt -X DELETE http://localhost:3000/me/sessions/def
# { "message": "Session revoked." }
# Sign out everywhere except this device
curl -b cookies.txt -X POST http://localhost:3000/me/sessions/revoke-all
# { "message": "All other sessions have been revoked." } Security details
Cannot revoke current session via DELETE. Use the normal logout endpoint for that. This prevents accidental self-logout from the session management UI.
“Sign out everywhere” keeps the current session. The user stays logged in on their current device. If they want to log out entirely, they use the logout endpoint after revoking all sessions.
The userId check in deleteDeviceSession prevents IDOR. A user cannot revoke another user’s session by guessing the session ID. The query includes user_id = ?.
Exercises
Exercise 1: Log in on two different “devices” (use different user agents with curl). List sessions. You should see both with different device names.
Exercise 2: Revoke one session. Try to use it (send a request with that session’s cookie). It should fail with 401.
Exercise 3: Use “revoke all.” The current session should still work. The other session should fail.
Why does 'sign out everywhere' keep the current session active?