Security
Threat model
This SDK handles authentication for web applications. The primary threats are:
- Token theft/replay: Magic-link tokens intercepted in transit or stolen from storage
- Session hijacking: Session cookies stolen via XSS, MITM, or cookie theft
- Brute force: Automated attempts to guess magic-link tokens
- Email enumeration: Attackers determining which emails have accounts
- CSRF: State-changing auth endpoints called by malicious sites
- Token reuse: Magic-link tokens consumed more than once
- Audit trail poisoning: Tokens or sensitive data leaking into logs
Security requirements (v1)
Token security
- Magic-link tokens MUST be hashed (SHA-256) before storage. Raw tokens are never persisted.
- Tokens MUST have a configurable TTL (default: 15 minutes)
- Tokens MUST be single-use. The consume operation MUST be atomic (D1 transaction, MySQL transaction) — no eventual-consistency window.
- Token generation MUST use
crypto.getRandomValues()with sufficient entropy (minimum 32 bytes, hex-encoded) - Expired tokens MUST be cleaned up on a schedule (D1: scheduled Worker, MySQL: cron/event)
Session security
- Session cookies MUST be
HttpOnly,Secure,SameSite=Lax, path/ - Cookie name MUST be configurable per host app (default:
jp_session) - Session IDs MUST be cryptographically random (minimum 32 bytes)
- Sessions MUST have a configurable TTL (default: 7 days)
- Session rotation: on every
completeLogin, issue a new session ID (never reuse) - Session revocation:
logoutMUST delete the session from storage AND clear the cookie - Provide a
revokeAllSessions(userId)method for emergency use
Rate limiting
-
requestLogin(magic-link send): rate limit per email (max 5 per 15 minutes) and per IP (max 20 per 15 minutes) -
completeLogin(token verification): rate limit per IP (max 10 per 15 minutes) - Rate limiting SHOULD use Cloudflare's built-in rate limiting or a D1-backed counter
- Rate limit responses MUST NOT leak whether the email exists
CSRF protection
- All state-changing auth endpoints (
requestLogin,completeLogin,logout) MUST verify origin - Use
Originheader check against a configurable allowed-origins list - For the subscribe widget: the form submission endpoint MUST also be origin-checked
Email enumeration resistance
-
requestLoginMUST return the same response regardless of whether the email exists - The subscribe widget intentionally has a known enumeration characteristic (different email content for known vs unknown users). This is documented as a deliberate tradeoff. Mitigation: identical UI response, identical subject line, identical email template structure.
Logging hygiene
- NEVER log raw tokens, session IDs, or magic-link URLs
- Log token prefixes only (first 8 characters) for debugging
- Log: email (hashed or truncated), action, timestamp, IP, result (success/failure/rate-limited)
- Structured JSON logging for machine parsing
Dependency management
- Renovate or Dependabot enabled on the repo
-
pnpm auditruns in CI on every PR - No runtime dependencies in
auth-core(contracts only) - Minimize dependencies in provider packages — prefer Web Crypto API and platform primitives
Deployment exposure
- Once build/deploy scaffolding stabilizes, disable the public
workers.devhostname and Preview URLs for production Workers, or place preview URLs behind Cloudflare Access so only authorized reviewers can bypass the custom domain.
Security non-goals (v1)
Things we're explicitly not building yet:
- MFA / TOTP / WebAuthn (defer to Firebase or BetterAuth adapters)
- Account lockout (rate limiting is sufficient for magic-link flows)
- IP allowlisting
- WAF-level protections (handled by Cloudflare at the edge)
- Penetration testing (manual review is sufficient at our current scale)
Security review process
Use .ai/prompts/security-review.md to run a security audit against any version of the codebase. The prompt is designed for a coding agent (Codex or Claude Code) to systematically check each item above.
Incident response
If a vulnerability is discovered:
- Determine blast radius (which host apps are affected)
- If token/session compromise: rotate all secrets, revoke all sessions via
revokeAllSessions - Fix in
auth-coreor relevant package - Bump version, deploy to all affected host apps
- Document in
SECURITY-CHANGELOG.md(create when first needed)