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

Session Security

Detecting suspicious sessions

With device tracking in place, you can flag sessions that look unusual:

// src/session-security.ts
import db from "./db.js";

export function getKnownIps(userId: string): Set<string> {
  const rows = db
    .prepare("SELECT DISTINCT ip FROM device_sessions WHERE user_id = ?")
    .all(userId) as { ip: string }[];
  return new Set(rows.map((r) => r.ip));
}

export function isNewIp(userId: string, ip: string): boolean {
  const known = getKnownIps(userId);
  return !known.has(ip);
}

After a successful login, check if the IP is new:

if (isNewIp(user.id, ip)) {
  log("new_ip_login", { userId: user.id, ip, deviceName });
  // In production: send a notification email
  console.log(`\n⚠️  New login for ${user.email} from ${ip} (${deviceName})\n`);
}

This does not block the login — IP changes are normal (VPNs, mobile networks, travel). But it alerts the user so they can check their sessions if the login was not them.

Idle timeout vs. absolute timeout

The auth course used a single MAX_AGE (24 hours). Production apps distinguish between two timeouts:

Absolute timeout: The session expires 24 hours after creation, regardless of activity. Forces periodic re-authentication. Prevents a session from being valid forever if the user stays active.

Idle timeout: The session expires after 30 minutes of inactivity. If the user walks away from a shared computer, the session closes automatically.

const ABSOLUTE_TIMEOUT = 24 * 60 * 60 * 1000; // 24 hours
const IDLE_TIMEOUT = 30 * 60 * 1000; // 30 minutes

export function isSessionValid(session: { createdAt: number; lastActiveAt: number }): boolean {
  const now = Date.now();

  // Absolute timeout
  if (now - session.createdAt > ABSOLUTE_TIMEOUT) return false;

  // Idle timeout
  if (now - session.lastActiveAt > IDLE_TIMEOUT) return false;

  return true;
}

Both timeouts are checked on every request. The session is rejected if either one has elapsed.

[!TIP] Idle timeouts are more aggressive for sensitive apps (banking: 5 minutes) and more lenient for consumer apps (social media: 7 days or no idle timeout). Choose based on the sensitivity of your data.

Session rotation after privilege changes

When the user’s privileges change (password change, role change, 2FA enabled/disabled), rotate the session ID. This prevents an old session (which might have been captured before the privilege change) from being reused.

export function rotateSession(oldSessionId: string, userId: string, request: Request): string {
  // Create a new session
  const newSessionId = createSession(userId);
  createDeviceSession(newSessionId, userId, request);

  // Delete the old session
  deleteSession(oldSessionId);
  deleteDeviceSession(oldSessionId, userId);

  return newSessionId;
}

Use it after sensitive operations:

// After password change:
const newSessionId = rotateSession(user.sessionId, user.id, c.request);
return Response.json(
  { message: "Password changed." },
  { headers: { "set-cookie": sessionCookie(newSessionId) } },
);

The user gets a new session cookie. The old session ID is invalid. Any attacker holding the old session is logged out.

Exercises

Exercise 1: Log in from a “new IP” (use X-Forwarded-For header). Check if the new-IP detection fires.

Exercise 2: Set the idle timeout to 5 seconds. Log in, wait 10 seconds, and make a request. The session should be rejected.

Exercise 3: Implement session rotation after a password change. Verify the old session is invalidated and the new one works.

Why do we use both idle and absolute timeouts instead of just one?

← Listing and Revoking Sessions What Is Step-Up Auth →

© 2026 hectoday. All rights reserved.