hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Production Auth Patterns with @hectoday/http

Before They Start

  • Why Production Auth Is Different
  • Project Setup

Email Verification

  • Why Verify Emails
  • Building Email Verification
  • Restricting Unverified Accounts

Session Management

  • Tracking Sessions Across Devices
  • Listing and Revoking Sessions
  • Session Security

Step-Up Authentication

  • What Is Step-Up Auth
  • Building Step-Up Auth
  • Applying Step-Up to Sensitive Routes

Account Deletion

  • The Right to Be Forgotten
  • Building Account Deletion
  • Data Cleanup

SAML and Enterprise SSO

  • What Is SAML
  • Building a SAML Service Provider
  • Just-in-Time Provisioning

Putting It All Together

  • Production Auth Checklist
  • Capstone: Production-Ready Auth

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?

← Tracking Sessions Across Devices Session Security →

© 2026 hectoday. All rights reserved.