Hub-and-spoke skill that converts "I need auth for app X" into a reviewable plan across 5 phases: intake (flows/stack/storage/MFA), identity-provider pick + env scaffold, session strategy + cookies, authorization model + permission matrix, and threats + mitigations. - 8 AskUserQuestion calls total (≥6 hub-and-spoke contract; 4 in Phase 1 + 1 each in Phases 2–5). - Reads all four _blocks/auth-*.md; never writes production code or secret values. - RULE 0.8 (Secrets SSoT): emits env VARIABLE NAMES only; storage path is secrets/auth.env per domain-has-secrets.md. - Constructor Pattern: 6 files, largest 115 LOC (<200 limit). - Fail-closed default + NO DOWNGRADE on unsafe combinations (passkey-only without recovery → return recovery-path options, not "not supported"). Evidence grade [E2] — pipeline mirrors OWASP ASVS v4.0.3 chapters 2–4.
3.4 KiB
3.4 KiB
Phase 3 — Session strategy + cookie configuration
Decides how the authenticated principal is carried across requests. Reads
_blocks/auth-sessions.md heavily. Default bias: server-side opaque
sessions (revocable, simple) unless the user needs horizontal stateless
scale.
3a — Strategy click (AskUserQuestion, single-select)
{
"questions": [
{
"question": "Session carrier?",
"header": "Session",
"multiSelect": false,
"options": [
{"label": "Server-side session + cookie (DEFAULT)",
"description": "Opaque 256-bit id in HttpOnly Secure cookie; row in DB/Redis. Instant revoke. Recommended for >95% of apps."},
{"label": "JWT access + opaque refresh",
"description": "ES256 access ≤15 min in HttpOnly cookie; refresh rotated server-side. Use ONLY when you have stateless edge workers that can't reach the session DB."},
{"label": "JWT access + refresh in native secure storage",
"description": "Mobile app; refresh in Keychain / Keystore. Same rotation rules; cookie flags N/A."},
{"label": "Managed (Clerk / Supabase / Auth0)",
"description": "Provider owns the session primitive; skill records the SDK integration points only."}
]
}
]
}
Store as SESSION.
3b — Emit cookie-flag checklist (inline, no AskUserQuestion)
Apply ONLY when SESSION involves a browser cookie. For every cookie the
app sets (session, CSRF, anti-re-use nonce):
[ ] HttpOnly — blocks JS read; XSS-resistant
[ ] Secure — HTTPS only; reject on cleartext
[ ] SameSite=Lax — default; use Strict for cross-site-hostile apps
[ ] __Host- prefix — no Domain, Path=/, Secure — session cookie only
[ ] Max-Age tuned — 7–30 d sliding (consumer) / 24 h hard (regulated)
[ ] Rotation on login, — new session_id issued, old row deleted or revoked_at set
logout-all, passkey/password change, privilege elevation
[ ] Logout deletes BOTH — server row AND cookie (Max-Age=0, same flags)
3c — Emit JWT-specific checklist (inline) — only if JWT chosen
[ ] Algorithm = ES256 — asymmetric; NOT HS256 for cross-service
[ ] access_token ≤15 min — minimises revocation-gap window
[ ] refresh_token OPAQUE — stored server-side, rotated on every use
[ ] refresh-reuse detection — family revocation on stolen refresh
[ ] JWKS rotation + kid — key rollover without service restart
[ ] Claims validated — iss, aud, exp, nbf, iat, sub, nonce (if OIDC)
[ ] NEVER in localStorage — HttpOnly cookie (web) / secure storage (native)
[ ] Logout policy stated — "revoke refresh only; access valid until exp"
and accepted by the product (or escalate to server-session strategy)
3d — CSRF strategy (inline, driven by SESSION)
- Cookie session + same-origin forms →
SameSite=Laxis enough; plus a CSRF token (cookie+header double-submit) for cross-origin POSTs. - Cookie session + third-party embed (iframes, extensions) →
SameSite=None; Secure+ mandatory CSRF token, reject missing/mismatched. - Bearer-token API (no cookie) → no CSRF (no ambient credential); enforce
Originheader check as defence-in-depth.
Verify-criterion
SESSIONset to exactly one strategy.- At least one of the three checklists (3b / 3c / 3d) applies and was emitted.
- If JWT chosen, 3c is printed in full AND the logout gap was explicitly acknowledged in the report.