name: implementing-authentication description: Implements identity verification for backend services — password storage, session cookies vs JWT, OAuth2/OIDC login flows, MFA, and password reset. Use when the user asks to add login, signup, authentication, sessions, JWTs, OAuth/OIDC or social login, password hashing, or password reset to a service. Do not use for permission checks after login (implementing-authorization) or for database credentials and roles (securing-databases).
Implementing Authentication
When to use / when NOT to use
- Use for: login/signup flows, password storage, session or token management, OAuth2/OIDC integration, MFA, password reset.
- Do NOT use for: deciding what a logged-in user may do (implementing-authorization), database users/roles (db-skills/securing-databases), or general service hardening (securing-backend-services).
Core rules
- Hash passwords with argon2id. bcrypt (cost ≥12) is the acceptable fallback when argon2 is unavailable.
- ✅
argon2id(password)via a maintained library - ❌
sha256(password + salt)— fast hashes are crackable at scale; never MD5/SHA-family alone
- ✅
- First-party web apps default to server-side sessions in cookies, not JWTs.
- Cookie flags always:
HttpOnly; Secure; SameSite=Lax(Strict for admin surfaces). - JWTs are for service-to-service and mobile/SPA APIs: expiry ≤15 min, paired with rotating refresh tokens, revocation list for logout.
- Cookie flags always:
- Third-party login uses OAuth2 authorization code + PKCE. Never the implicit flow; never roll your own OAuth client if the framework has one.
- Rate-limit credential endpoints (login, signup, reset): per-IP and per-account. Check new passwords against a breach corpus (e.g. haveibeenpwned k-anonymity API).
- Password reset: single-use token, expiry ≤1 hour, stored hashed, sent by email link only.
- ✅ Response is identical whether the account exists or not ("If that address exists, we sent a link.")
- ❌ "No account with that email" — user enumeration
- MFA hooks: TOTP as default second factor; enforce at login and before sensitive actions (payout, email change). Recovery codes generated once, stored hashed.
- Never log or echo credentials, tokens, or session IDs. Rotate the session ID on privilege change (login, MFA pass) to block session fixation.
Workflow
- Pick the mechanism with the decision rule in rule 2 (sessions vs JWT vs OIDC).
- Implement storage: user table with
password_hash(argon2id), no plaintext or reversible encryption anywhere. - Implement the flow with the framework's primitives (e.g. FastAPI +
authlib, Express +passport); wire rate limits (rule 4). - Add reset + MFA per rules 5–6.
- Validate: attempt login with wrong password (must fail generically), replay an expired/rotated token (must fail), inspect the Set-Cookie header for
HttpOnly; Secure; SameSite, and confirm the reset flow gives identical responses for existing and unknown emails. Fix and re-test until all four pass.
Edge cases & failure modes
- Existing weak hashes (MD5/SHA1) → rehash transparently on next successful login; force reset for dormant accounts.
- Clock skew with JWTs → allow ≤60 s leeway on
exp/nbf, never more. - Lockout abuse (attacker locking victims out) → prefer progressive delays + CAPTCHA over hard lockout.
- OAuth provider returns unverified email → treat as unverified; require confirmation before linking accounts.
References
Copy-paste patterns and gotchas: see references/patterns.md.