Skip to main content

Source: ocean/docs/SECURITY_FALLBACK_ANALYSIS.md | ✏️ Edit on GitHub

Security Analysis: Fallback Risks in JWT Verification

Potential Attack Vectors

1. Downgrade Attacks

Risk: Attacker forces system to use weaker authentication method

  • Force JWT verification to fail, triggering fallback to database lookup
  • Exploit timing differences between local verification and API calls
  • DoS the JWKS endpoint to force fallbacks

2. Token Confusion

Risk: Different validation rules between primary and fallback

  • JWT verifier might have stricter rules than Supabase's getUser()
  • Expired tokens might pass one check but not the other
  • Role/permission mismatches between methods

3. Cache Poisoning

Risk: Attacking the JWKS cache

  • Serve invalid keys to make all tokens fail verification
  • Force system into fallback mode consistently
  • Exploit cache TTL windows

4. Timing Attacks

Risk: Behavioral differences reveal information

  • Failed JWT verification takes different time than API fallback
  • Can enumerate valid vs invalid tokens
  • Can detect when system is in fallback mode
// SECURE: Fail closed, no fallbacks
export class SecureAuthService {
async verifyToken(token: string): Promise<AuthResult> {
try {
const result = await jwtVerifier.verifyToken(token)

if (!result.valid) {
// FAIL CLOSED - No fallback
await this.logSecurityEvent('jwt_verification_failed', { token })
throw new AuthenticationError('Invalid token')
}

return { success: true, user: result.payload }
} catch (error) {
// FAIL CLOSED - No fallback
await this.logSecurityEvent('jwt_verification_error', { error })
throw new AuthenticationError('Authentication failed')
}
}
}

Security-First Design

1. Single Source of Truth

  • JWT verification is THE authentication method
  • No fallbacks, no alternate paths
  • Consistent security posture

2. Fail Closed

  • Any failure = access denied
  • No degraded modes
  • No "helpful" workarounds

3. Monitoring Required

interface SecurityEvent {
event: 'jwt_verification_failed' | 'jwks_fetch_failed' | 'token_expired'
timestamp: Date
metadata: Record<string, any>
}

class SecurityMonitor {
async logEvent(event: SecurityEvent) {
// Log to SIEM
// Alert on anomalies
// Track failure rates
}
}

Migration Strategy Without Fallbacks

Phase 1: Parallel Validation (Monitoring Only)

// Run both, but only use Supabase result
const jwtResult = await jwtVerifier.verifyToken(token)
const supabaseResult = await supabase.auth.getUser(token)

// Log discrepancies but don't act on them
if (jwtResult.valid !== !!supabaseResult.data.user) {
await logDiscrepancy({ jwtResult, supabaseResult })
}

// Still use Supabase for now
return supabaseResult

Phase 2: JWT Primary with Kill Switch

const FEATURE_FLAGS = {
useJwtVerification: true, // Can disable in emergency
}

if (FEATURE_FLAGS.useJwtVerification) {
return await jwtVerifier.verifyToken(token)
} else {
return await supabase.auth.getUser(token)
}

Phase 3: JWT Only

  • Remove Supabase auth calls
  • Remove feature flags
  • Pure JWT verification

Key Principles

  1. Never Mix Authentication Methods

    • Choose one method and stick with it
    • Mixing creates complexity and attack surface
  2. Fail Closed Always

    • No token = no access
    • Invalid token = no access
    • Verification error = no access
  3. Monitor Everything

    • Track all failures
    • Alert on anomalies
    • Have incident response ready
  4. Test Security Scenarios

    describe('Security Tests', () => {
    it('should reject when JWKS is unavailable', async () => {
    // Block JWKS endpoint
    await expect(authService.verifyToken(validToken)).rejects.toThrow('Authentication failed')
    })

    it('should reject expired tokens', async () => {
    await expect(authService.verifyToken(expiredToken)).rejects.toThrow('Authentication failed')
    })
    })

Recommendation

Remove all fallbacks from the enhanced auth service. The JWT verifier should be the sole authentication method. If it fails, access is denied. This is more secure than trying to be "helpful" with fallbacks.