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.
Critical because authentication weakness directly enables account takeover — bypassing every downstream access control in the system simultaneously.
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:
// 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.
ID: gov-cmmc-level-1.identification-auth.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:
// 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
}