# Concurrent sessions limited to 1, warning on new login

- **Pattern:** `ab-001469` (`finserv-session-security.concurrent-control.concurrent-session-limit`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001469
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001469)

## Why it matters

Unlimited concurrent sessions mean a stolen session token can be used in parallel with the legitimate user's session — the account owner has no indication the account is being accessed simultaneously from a different location. CWE-613 covers this under insufficient session expiration. PCI-DSS Req 8.2.8 and NIST AC-10 both address concurrent session controls. In financial applications, the attack pattern is: steal session token, establish parallel session, initiate transfer while the legitimate user is actively transacting and won't notice a concurrent login warning. Limiting concurrent sessions to one and notifying on new logins closes this window — the attacker's session either terminates or the user receives a warning.

## Severity rationale

High because unlimited concurrent sessions allow a stolen token to establish a parallel, invisible session that can execute financial operations at the same time as the legitimate user with no signal to either party.

## Remediation

Enforce the single-session limit in `src/middleware/concurrentSessionCheck.ts` by checking active session count on each new login and either terminating prior sessions or alerting the user:

```js
export async function enforceSessionLimit(
  userId: string,
  currentSessionId: string
) {
  const active = await db.sessions.findMany({
    where: { userId, expiresAt: { gt: new Date() } }
  });

  if (active.length > 0) {
    await notifyUser(userId, 'New login detected — prior session terminated.');
    await db.sessions.updateMany({
      where: { userId, id: { not: currentSessionId } },
      data: { terminatedAt: new Date() }
    });
  }
}
```

Call `enforceSessionLimit` immediately after session creation in the login handler, before returning the session token to the client. Store sessions server-side (Redis or Postgres) so termination is authoritative — client-side token deletion alone is insufficient.

## Detection

- **ID:** `concurrent-session-limit`
- **Severity:** `high`
- **What to look for:** Count all active session tracking mechanisms and quote the actual session limit setting found. Enumerate the enforcement logic: does a new login terminate or warn existing sessions? Verify the concurrent session limit is no more than 1 for financial operations. An unlimited concurrent session policy does not count as pass — do not pass if users can maintain more than 1 active session.
- **Pass criteria:** Concurrent session limit is enforced at no more than 1 active session per user for financial operations. When a new login occurs, the previous session is terminated or user is warned. Report the count even on pass (e.g., "Session limit: 1, previous session terminated on new login, warning notification sent").
- **Fail criteria:** Multiple concurrent sessions allowed (no limit enforced), or new login does not affect existing sessions, or limit exceeds 1 for financial contexts.
- **Skip (N/A) when:** The application does not require per-user session limits — cite the actual security requirements found.
- **Detail on fail:** `"No concurrent session limit — 0 enforcement mechanisms. User can maintain unlimited simultaneous sessions from different IPs."`
- **Cross-reference:** Check `finserv-session-security.concurrent-control.user-view-terminate-sessions` for user session management, and `finserv-session-security.concurrent-control.login-location-tracking` for location awareness.
- **Remediation:** Enforce single-session-per-user limit (in `src/middleware/concurrentSessionCheck.ts`):
  ```js
  // middleware/concurrentSessionCheck.ts
  export async function enforceSessionLimit(userId: string) {
    const activeSessions = await db.sessions.findMany({
      where: { userId, expiresAt: { gt: new Date() } }
    });

    if (activeSessions.length > 0) {
      // Notify user of new login attempt
      await notifyUser(userId, `New login from ${deviceInfo.location}`);
      // Option 1: Auto-terminate previous sessions
      await db.sessions.updateMany({
        where: { userId, id: { not: currentSessionId } },
        data: { terminatedAt: new Date() }
      });
    }
  }
  ```

## External references

- cwe CWE-613
- owasp A07
- pci-dss Req 8.2.8
- nist AC-10

Taxons: access-control

HTML version: https://auditbuffet.com/patterns/ab-001469
