# Identity verified (passwords, sessions) before access

- **Pattern:** `ab-001555` (`gov-cmmc-level-1.identification-auth.authentication-verify`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001555
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001555)

## Why it matters

CMMC 2.0 IA.L1-3.5.2 (NIST 800-171r2 3.5.2) requires that organizational systems authenticate users, devices, and processes before granting access. Plaintext or weakly hashed passwords (MD5, SHA-1 without salt) allow an attacker who obtains the database to crack every credential in minutes using precomputed rainbow tables. Storing authentication tokens in localStorage instead of HttpOnly cookies exposes them to any XSS payload. Sessions without expiration persist indefinitely after a user's contract access is revoked. CWE-256 (Plaintext Storage of a Password) and OWASP A07 (Identification and Authentication Failures) directly name each of these failure modes.

## Severity rationale

Critical because authentication weakness directly enables account takeover — bypassing every downstream access control in the system simultaneously.

## Remediation

Use bcrypt or argon2 for password hashing with a cost factor calibrated to at least 250ms on your server hardware. Set session cookies with `HttpOnly`, `Secure`, and `SameSite=Lax` — and a `Max-Age` no longer than 30 days:

```ts
// lib/auth/password.ts
import bcrypt from 'bcryptjs'
const SALT_ROUNDS = 12
export const hashPassword = (p: string) => bcrypt.hash(p, SALT_ROUNDS)
export const verifyPassword = (p: string, hash: string) => bcrypt.compare(p, hash)

// app/api/auth/login/route.ts
export async function POST(req: Request) {
  const { email, password } = await req.json()
  const user = await db.user.findUnique({ where: { email } })
  // Always run compare even for unknown emails — prevents timing attacks
  const valid = user ? await verifyPassword(password, user.passwordHash) : false
  if (!user || !valid) {
    return Response.json({ error: 'Invalid email or password' }, { status: 401 })
  }
  const token = crypto.randomUUID()
  const res = Response.json({ ok: true })
  res.headers.set('Set-Cookie',
    `session=${token}; HttpOnly; Secure; SameSite=Lax; Max-Age=1800; Path=/`
  )
  return res
}
```

If using Supabase Auth, NextAuth, or Clerk, verify the provider is not bypassed by any custom login route.

## Detection

- **ID:** `authentication-verify`
- **Severity:** `critical`
- **CMMC Practice:** IA.L1-3.5.2
- **What to look for:** Examine the login flow, password storage, and session management. Look for password hashing using bcrypt, argon2, or scrypt — never plaintext or weak hashes (MD5, SHA-1 without salt). Verify that the login handler compares submitted passwords against stored hashes server-side. Check cookie settings: `HttpOnly` prevents JavaScript access, `Secure` ensures HTTPS-only transmission, and a `SameSite` attribute prevents CSRF. Look for session expiration configuration. If using an auth library (Supabase Auth, NextAuth, Clerk, Auth0), verify it is configured correctly rather than bypassed.
- **Pass criteria:** List all authentication mechanisms found (hashing algorithm, session storage, cookie flags). Passwords are hashed with bcrypt, argon2, or scrypt before storage. Sessions use HttpOnly, Secure cookies with a reasonable expiration of no more than 30 days. Report even on pass: "Authentication uses [algorithm] hashing with [session type] sessions."
- **Fail criteria:** Passwords stored in plaintext or hashed with MD5/SHA-1 (without salt). Authentication checks performed only on the client side. Sessions stored in localStorage instead of HttpOnly cookies. No session expiration configured.
- **Skip (N/A) when:** Never — identity verification is a core CMMC Level 1 requirement.
- **Detail on fail:** Identify the specific authentication weakness. Example: `"lib/auth.ts stores passwords using SHA-1 without salt. Login comparison done client-side in LoginForm.tsx — server never verifies credentials."` Keep under 500 characters.
- **Remediation:** Use a proven password hashing library and enforce secure session cookies:

  ```ts
  // lib/auth/password.ts
  import bcrypt from 'bcryptjs'

  const SALT_ROUNDS = 12 // ~250ms on modern hardware — good balance of security and performance

  export async function hashPassword(password: string): Promise<string> {
    return bcrypt.hash(password, SALT_ROUNDS)
  }

  export async function verifyPassword(password: string, hash: string): Promise<boolean> {
    return bcrypt.compare(password, hash)
  }

  // app/api/auth/login/route.ts
  export async function POST(req: Request) {
    const { email, password } = await req.json()

    const user = await db.user.findUnique({ where: { email } })
    // Always run bcrypt.compare even if user not found — prevents timing attacks
    const valid = user ? await verifyPassword(password, user.passwordHash) : false

    if (!user || !valid) {
      // Same error for missing user AND wrong password — prevents enumeration
      return Response.json({ error: 'Invalid email or password' }, { status: 401 })
    }

    // Create session with HttpOnly, Secure cookie
    const sessionToken = crypto.randomUUID()
    // Store session in database...
    const response = Response.json({ ok: true })
    response.headers.set(
      'Set-Cookie',
      `session=${sessionToken}; HttpOnly; Secure; SameSite=Lax; Max-Age=1800; Path=/`
    )
    return response
  }
  ```

## External references

- cmmc IA.L1-3.5.2
- cwe CWE-256
- cwe CWE-614
- owasp A07
- nist SP-800-171 3.5.2

Taxons: access-control, cryptography-and-secrets

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