Skip to content

Security

Overview

CRED Auth implements defense-in-depth with multiple security layers applied at the framework, transport, and application levels.


HTTP Security Headers (Helmet)

Helmet is configured in main.ts with a strict Content Security Policy:

Directive Value Rationale
default-src 'self' Only load resources from same origin
script-src 'self' No inline scripts
style-src 'self' 'unsafe-inline' Allow inline styles for login forms
frame-src 'none' No iframes
frame-ancestors 'none' Clickjacking protection
form-action Disabled Required for OIDC redirects to custom-scheme URIs (cursor://, vscode://)
upgrade-insecure-requests Enabled in production Force HTTPS

Additional headers:

  • HSTS — 1 year with includeSubDomains and preload (production only)
  • X-Content-Type-Optionsnosniff
  • Referrer-Policystrict-origin-when-cross-origin
  • Cross-Origin-Opener-Policysame-origin-allow-popups (for OAuth popup flows)
  • Cross-Origin-Resource-Policysame-site

CORS

Configured via the CORS_ORIGINS environment variable (comma-separated origins).

Environment Behavior
Development All origins allowed (if CORS_ORIGINS is empty)
Production Only explicitly listed origins; falls back to disabled

Allowed headers: Content-Type, Authorization, X-CSRF-Token, X-Session-Id


Rate Limiting

Global rate limiting via @nestjs/throttler, registered as an APP_GUARD. The /oauth/* path is excluded from in-app throttling (handled externally or by oidc-provider).

Global Throttlers

Tier Window Limit Env Variables
Short (burst) 10 seconds 10 requests THROTTLE_SHORT_TTL, THROTTLE_SHORT_LIMIT
Medium 1 minute 100 requests THROTTLE_MEDIUM_TTL, THROTTLE_MEDIUM_LIMIT
Long 1 hour 1,000 requests THROTTLE_LONG_TTL, THROTTLE_LONG_LIMIT

Sensitive Endpoint Throttlers

Tier Window Limit Env Variables
Per minute 1 minute 5 requests THROTTLE_LOGIN_PER_MINUTE
Per hour 1 hour 20 requests THROTTLE_LOGIN_PER_HOUR

Route-Level Decorators

Decorator Applied To Policy
@ThrottleLogin() POST /auth/login, POST /login/:uid/submit Strict (5/min, 20/hour)
@ThrottleApi() GET /auth/me Relaxed (authenticated callers)
@ThrottleRelaxed() GET / High limits
@ThrottleDiscovery() .well-known/* Moderate limits

CSRF Protection

Stateless HMAC-signed CSRF tokens — no shared storage (Redis/sessions) required, making it suitable for multi-instance deployments.

How It Works

  1. Server generates a signed token containing timestamp.sessionId.nonce
  2. Token is embedded in the login/consent form as a hidden field (_csrf)
  3. On form submission, CsrfGuard validates the token signature, session binding, and expiration

Token Delivery

Channel Field
Form body _csrf
Request header X-CSRF-Token
Session ID source Route param :uid or X-Session-Id header

Secret Rotation

Supports zero-downtime secret rotation:

  1. Add new secret as first element in OAUTH_COOKIE_KEYS array
  2. Keep old secret as second element during the rotation window (>= CSRF_TOKEN_TTL)
  3. Remove old secret after the window expires

New tokens are always signed with the current (first) secret. Verification tries current first, then falls back to previous.

Configuration

Variable Default Description
CSRF_SECRET Dedicated CSRF secret (takes precedence)
OAUTH_COOKIE_KEYS Falls back to first two keys from this array
CSRF_TOKEN_TTL 900000 (15 min) Token validity window in milliseconds

Input Validation

Global ValidationPipe configured with:

Option Value Effect
whitelist true Strips unknown properties from payloads
forbidNonWhitelisted true Rejects requests with unknown properties
transform true Auto-transforms payloads to DTO instances
disableErrorMessages true in production Hides detailed validation errors

Warning

enableImplicitConversion is intentionally disabled to prevent silent type coercion bugs in auth flows. Use explicit @Transform decorators in DTOs when needed.


Authentication

Password Hashing

  • Algorithm: bcrypt via bcryptjs
  • Timing-safe: Failed lookups still perform a dummy bcrypt compare to prevent timing attacks
  • Inactive users: Rejected before credential check (isActive flag on User)
Setting Value
httpOnly true
sameSite lax
secure true in production
path /

Cookies are signed with keys from OAUTH_COOKIE_KEYS (JSON array, each key at least 32 characters).


Proxy Trust

When deployed behind a reverse proxy (Cloud Run, nginx), set TRUST_PROXY=true to trust X-Forwarded-For headers. This affects:

  • Rate limiting (correct client IP detection)
  • oidc-provider redirect URL generation
  • HSTS and secure cookie behavior