Skip to main content

Source: ocean/docs/adr/0006-fail-closed-authentication.md | ✏️ Edit on GitHub

ADR-0006: Fail-Closed Authentication Without Fallbacks

Status

Accepted

Context

The user raised a critical security concern: "are fallbacks a vector for risk?" This prompted a comprehensive security analysis of our authentication system, which previously included fallback mechanisms that could potentially be exploited.

Security Risks Identified

  1. Downgrade Attacks: Attackers could force the system to use weaker authentication methods by making primary verification fail
  2. Token Confusion: Different validation rules between primary and fallback methods could be exploited
  3. Cache Poisoning: Attacking the JWKS cache to force consistent fallback mode
  4. Timing Attacks: Behavioral differences revealing information about valid vs invalid tokens

Decision

Implement a fail-closed authentication service with zero fallbacks. If JWT verification fails for any reason, access is denied. No alternate authentication paths, no degraded modes, no "helpful" workarounds.

Consequences

Positive

  • Stronger Security Posture: No attack vectors through fallback mechanisms
  • Consistent Behavior: Single authentication path reduces complexity
  • Clear Failure Modes: Any failure = access denied, no ambiguity
  • Audit Trail: Comprehensive security event logging for monitoring

Negative

  • Less Resilient: If JWKS endpoint is down, all authentication fails
  • No Graceful Degradation: Users locked out during any verification issues
  • Stricter Requirements: All tokens must pass verification, no exceptions

Implementation

Core Principles

  1. Single Source of Truth: JWT verification is THE authentication method
  2. Fail Closed Always: Any error or invalid token = access denied
  3. Comprehensive Logging: All security events logged for monitoring
  4. No Fallbacks: Removed all alternate authentication paths

Code Structure

export class SecureAuthService {
async getSession(): Promise<{ session: Session | null; error?: Error }> {
// 1. Get session from Supabase
// 2. Verify JWT - if fails, clear session and deny access
// 3. Return session only if verification succeeds
}

async getUserFromToken(token: string): Promise<{ user: User | null; error?: Error }> {
// 1. Verify and extract user from JWT
// 2. Validate required fields (id, email)
// 3. Fail closed on any error
}
}

Security Event Types

  • jwt_verification_failed: Token verification failed
  • jwt_verification_error: Error during verification
  • jwks_fetch_failed: Could not fetch public keys
  • session_fetch_failed: Could not get session
  • user_extraction_failed: Could not extract user from token
  • invalid_user_data: User data incomplete
  • refresh_token_invalid: Refreshed token failed verification
  • auth_state_invalid_token: Auth state change with invalid token

Migration Path

  1. Deploy new secure auth service alongside existing
  2. Monitor security events to understand failure patterns
  3. Switch to secure auth service with feature flag
  4. Remove old auth code once stable

Alternatives Considered

1. Fallback to Database Lookup

Rejected: Creates downgrade attack vector where attacker forces JWT verification to fail

2. Cached User Sessions

Rejected: Cache poisoning could allow invalid sessions to persist

3. Emergency Override Tokens

Rejected: Backdoor that undermines entire security model

References