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
includeSubDomainsandpreload(production only) - X-Content-Type-Options —
nosniff - Referrer-Policy —
strict-origin-when-cross-origin - Cross-Origin-Opener-Policy —
same-origin-allow-popups(for OAuth popup flows) - Cross-Origin-Resource-Policy —
same-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
- Server generates a signed token containing
timestamp.sessionId.nonce - Token is embedded in the login/consent form as a hidden field (
_csrf) - On form submission,
CsrfGuardvalidates 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:
- Add new secret as first element in
OAUTH_COOKIE_KEYSarray - Keep old secret as second element during the rotation window (>=
CSRF_TOKEN_TTL) - 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 (
isActiveflag on User)
Cookie Security
| 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-providerredirect URL generation- HSTS and secure cookie behavior