# Authentication

> Choose and implement auth correctly — JWT vs session vs OAuth decision, pin allowed algorithms server-side, rotate refresh tokens with reuse detection, avoid the classic JWT pitfalls. Use when adding login, integrating OAuth, or when token handling looks risky. Not for access control / permissions (use authorization) or a broader OWASP audit (use backend-security-audit).

- Skill: `jaykim88/authentication` (Agent Skill)
- Install (CLI): `npx skillmds@latest add jaykim88/authentication`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jaykim88/authentication/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- License: MIT
- Author: JayKim88 (https://skillmd.com/u/jaykim88)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/jaykim88/authentication

---


# Authentication

## Purpose
Establish who the caller is, securely. Pick the right mechanism for the use case and avoid the well-known token pitfalls (algorithm confusion, non-rotating refresh tokens, client-trusted claims).

**Universal** — the JWT-vs-session-vs-OAuth decision, algorithm pinning, and refresh-rotation-with-reuse-detection are auth principles independent of language; the library differs.

## Procedure

1. **Choose the mechanism by use case**
   - **Session (server-side, cookie)** — classic web apps, easy revocation, default for first-party
   - **JWT (stateless)** — APIs, microservices, mobile; harder to revoke (keep access tokens short)
   - **OAuth 2.0 / OIDC** — delegated auth (login with Google), third-party access
   - **Passkeys / WebAuthn** — passwordless, phishing-resistant. Treat as the *primary* path for new consumer products; passwords as a fallback. The provider (Supabase Auth / Auth.js / similar) handles the protocol — your job is the UX
   - Don't default to JWT for a simple web app — sessions are simpler and revocable

1b. **Require MFA / step-up for sensitive operations**
   - At least a second factor (TOTP, passkey, push) for sign-in to anything with money, PII at scale, or admin scope
   - **Step-up** (re-auth before a high-stakes action like withdrawal / password change) is more usable than "always-MFA-on-everything"
   - Recovery flow is part of the design — account-recovery is the typical bypass attackers use, not the front door

2. **Pin allowed algorithms server-side — never trust the token `alg` header**
   - Classic attack class: **algorithm confusion** (RS256→HS256, using the public key as an HMAC secret) and the **`none` algorithm** bypass
   - Explicitly configure the verifier to accept ONLY your chosen algorithm; reject everything else
   - (Encode the *vulnerability class*, not specific CVE numbers — those churn)

3. **Short access tokens + rotating refresh tokens**
   - Access token: short TTL (minutes) so a leaked token expires fast
   - Refresh token: rotate on every use; **detect reuse** (a used-twice refresh token = theft → revoke the whole token family)

4. **Store tokens safely (coordinate with frontend)**
   - Refresh/session tokens in HttpOnly + Secure + SameSite cookies — never localStorage (XSS-readable)
   - **Rotate the session id on every login (and privilege change)** — pinning a pre-auth session id post-login = session fixation
   - This pairs with the frontend-toolkit `security-audit` token-storage rule

5. **Validate token claims as untrusted input**
   - Verify `exp`, `iss`, `aud`, signature — all of them
   - Treat claims as untrusted until verified (see `data-validation`)

5b. **Defend the login endpoint itself**
   - Rate-limit by IP **and** by account (credential-stuffing attacks rotate IPs; per-account limits stop them)
   - Account lockout / exponential delay after N failures; expose only generic "invalid credentials" (no user-enumeration leak between "no such user" and "wrong password")
   - On OAuth flows: `state` (CSRF) + PKCE for public clients; verify the redirect URI is on the allowlist exactly (not a prefix)

6. **Validate (validation loop)**
   - Attempt an `alg: none` and an RS256→HS256 confusion token → verify both rejected
   - Use a refresh token twice → verify reuse detection revokes the family
   - If any forged/replayed token is accepted → verifier misconfigured; fix and re-test

## Anti-patterns

| ❌ Anti-pattern | ✅ Correct |
|---|---|
| Trusting the token's `alg` header | Pin allowed algorithms server-side |
| Long-lived access tokens, no rotation | Short access + rotating refresh w/ reuse detection |
| Tokens in localStorage | HttpOnly + Secure + SameSite cookies |
| JWT for a simple revocable web session | Server-side session (easy revocation) |
| Skipping `iss`/`aud`/`exp` verification | Verify all standard claims |
| Reusing the pre-login session id post-login | Rotate session id on login (session fixation defense) |
| No login rate-limit / generic 401 only by IP | Rate-limit by IP **and** per account; lockout after N failures |
| User-enumeration leak ("no such user" vs "wrong password") | Generic "invalid credentials" for both |

## Severity tiers

| Tier | Examples | Action SLA |
|---|---|---|
| **Critical** | `alg: none` or RS256→HS256 accepted; refresh token never rotates; tokens in localStorage; no MFA on admin / money-moving actions | Block release; fix immediately |
| **Major** | Long access-token TTL with no revocation; missing `aud`/`iss` checks; no login rate-limit (credential stuffing open); session id not rotated on login (fixation) | Fix this sprint |
| **Minor** | Session timeout untuned; OAuth state param missing on a low-risk flow; user-enumeration leak in error messages | Schedule within 2 sprints |

## Stop & Ask (AI must pause for user approval)

- **Before rotating a signing key in production** — confirm a multi-key verifier window so in-flight tokens validate during cutover
- **Before changing the allowed `algorithms` list on the verifier** — confirm every live issuer is already on the new algorithm
- **Before mass-revoking refresh-token families** — this logs users out everywhere; coordinate the user-comms

## Completion Criteria
- [ ] Mechanism choice justified by use case (passkeys considered for consumer products)
- [ ] MFA / step-up required on admin and money-moving actions; recovery flow explicitly designed
- [ ] Allowed algorithms pinned (forged-token rejection verified)
- [ ] Refresh rotation + reuse detection in place; session id rotates on login
- [ ] Login endpoint: rate-limited by IP **and** per account; generic error message (no user enumeration)
- [ ] Tokens stored in HttpOnly cookies (not localStorage)
- [ ] All standard claims verified

## Output
- **Auth implementation**: chosen mechanism + token lifecycle
- **Security notes**: algorithm pinning + rotation policy documented
- **Commit format**: `feat(auth): add <mechanism> login` / `fix(auth): pin allowed JWT algorithms`

## Implementation

### TypeScript + Supabase Auth / NestJS (default)
- **Supabase Auth** (default): handles JWT issuance, refresh rotation, OAuth providers, RLS integration — prefer it over hand-rolling
- NestJS: `@nestjs/passport` + `passport-jwt`; configure `algorithms: ['RS256']` explicitly (pin)
- Refresh rotation + reuse detection: Supabase built-in, or implement token-family tracking
- **Lucia is deprecated** (sunset Jul 2025) — read it as a reference, don't adopt; use Supabase Auth / Auth.js

### Other stacks
- **Python / FastAPI**: `fastapi-users`; PyJWT with explicit `algorithms=[...]`
- **Go**: `golang-jwt` with explicit alg pinning; avoid the historical alg-confusion footgun
- **Universal**: algorithm pinning + refresh rotation + reuse detection are protocol-level; the `none`-alg and RS256→HS256 classes apply to every JWT library

## Related skills
- `authorization` — authn (who you are) precedes authz (what you can do)
- `backend-security-audit` — token handling is a top OWASP audit area
- `data-validation` — token claims are untrusted input — validate them

## Reference
- **Key insight encoded**: Always pin allowed algorithms server-side (never trust the token header `alg`); rotate refresh tokens with reuse detection that revokes the whole family on replay. Modern auth is more than tokens: passkeys for consumer products, MFA / step-up on sensitive actions, login-endpoint rate-limit by IP **and** per account (credential-stuffing rotates IPs), and session-id rotation on login (fixation).
- **Caveats**: Lucia is deprecated — recommend Supabase Auth / Auth.js. Encode the *vulnerability class* (none-alg, RS256→HS256) not specific CVE IDs (search-returned CVE numbers were unreliable).

