Authentication Flow Errors - FeitianTech/postquantum-webauthn-platform GitHub Wiki
- Introduction
- Common Authentication Flow Error Types
- Challenge-Response Mismatch Issues
- Origin Validation Failures
- User Presence Requirement Errors
- CBOR Encoding/Decoding Problems
- WebAuthn API Error Codes
- Credential Creation Options Validation
- Browser Developer Tools Integration
- User Verification and Resident Key Issues
- Attestation Statement Format Validation
- Debugging Strategies and Best Practices
- Common Client-Side Failure Points
- Common Server-Side Failure Points
- Troubleshooting Checklist
Authentication flow errors in WebAuthn implementations can occur at multiple layers, from client-side JavaScript to server-side validation and CTAP2 command processing. This comprehensive guide covers the most common error scenarios, their root causes, and systematic debugging approaches to resolve them effectively.
The WebAuthn authentication flow involves several critical steps: challenge generation, client-side assertion creation, server-side validation, and attestation verification. Each step can introduce potential failure points that manifest as different types of errors.
Registration errors typically occur during the credential creation ceremony and include:
- Challenge validation failures: When the client's response doesn't match the expected challenge
- Origin validation errors: When the origin in the client data doesn't match expectations
- RP ID hash mismatches: When the relying party ID hash doesn't align with expectations
- User verification requirement violations: When required user verification isn't satisfied
- Attestation format validation errors: When attestation statements fail validation
Authentication errors occur during the assertion verification process:
- User presence flag violations: When the user presence flag isn't set appropriately
- User verification requirement violations: When required user verification isn't performed
- Signature verification failures: When the cryptographic signature doesn't validate
- Counter increment issues: When the signature counter doesn't increase as expected
Low-level hardware communication errors:
- Command parsing failures: When CBOR commands aren't properly formatted
- Device capability mismatches: When requested operations aren't supported
- Timeout errors: When operations exceed expected time limits
- Device state inconsistencies: When device state doesn't match expected conditions
Challenge-response mismatches are among the most common authentication failures. They occur when the challenge value included in the client data doesn't match the expected challenge stored during the ceremony initiation.
sequenceDiagram
participant Client as "Client Browser"
participant Server as "Authentication Server"
participant Device as "Authenticator Device"
Server->>Client : Send challenge + options
Client->>Device : Request assertion
Device->>Client : Generate response with challenge
Client->>Server : Submit response + challenge
Server->>Server : Validate challenge matches
Note over Server : Challenge mismatch error occurs here
Diagram sources
- fido2/server.py
- server/server/attestation.py
The challenge validation occurs in multiple locations:
-
Basic challenge comparison:
# From fido2/server.py if websafe_decode(state["challenge"]) != client_data.challenge: raise ValueError("Wrong challenge in response.")
-
Advanced challenge validation:
# From server/attestation.py if expected_challenge_bytes and not challenge_matches: results["errors"].append("challenge_mismatch")
- Encoding differences: Base64 vs Base64URL encoding variations
- Challenge expiration: Challenges that expire before submission
- State corruption: Session state not properly maintained
- Cross-origin issues: Challenges generated for different origins
-
Log challenge values:
console.log('Generated challenge:', generatedChallenge); console.log('Submitted challenge:', submittedChallenge);
-
Compare encoded values:
function compareChallenges(generated, submitted) { const genBytes = websafe_decode(generated); const subBytes = websafe_decode(submitted); return genBytes.toString() === subBytes.toString(); }
-
Validate challenge timing:
# Check challenge expiration challenge_expiration = state.get("challenge_expires") if challenge_expiration and challenge_expiration < datetime.utcnow(): return "Challenge expired"
Section sources
- fido2/server.py
- server/server/attestation.py
Origin validation ensures that authentication requests come from legitimate sources and prevents phishing attacks. The origin validation process checks that the client data's origin matches the expected origin for the relying party.
flowchart TD
A["Client Data Received"] --> B["Extract Origin"]
B --> C["Normalize Origin"]
C --> D{"Origin Matches<br/>Expected Value?"}
D --> |No| E["Origin Validation Failed"]
D --> |Yes| F["Origin Validation Passed"]
E --> G["Return Error Response"]
F --> H["Continue Processing"]
Diagram sources
- fido2/server.py
- server/server/attestation.py
Cross-origin requests can cause validation failures:
// Example of cross-origin detection
if (clientData.cross_origin) {
results["errors"].append("cross_origin_not_allowed");
}Different browsers may report origins differently:
- Scheme differences: http vs https
- Port inclusion: localhost:3000 vs localhost
- Trailing slashes: example.com vs example.com/
-
Log origin values:
console.log('Expected origin:', expectedOrigin); console.log('Actual origin:', clientData.origin);
-
Normalize origins for comparison:
function normalizeOrigin(origin) { try { const url = new URL(origin); return `${url.protocol}//${url.hostname}${url.port ? ':' + url.port : ''}`; } catch (e) { return origin.toLowerCase(); } }
-
Validate origin against allowed list:
allowed_origins = ["https://example.com", "https://demo.example.com"] if client_data.origin not in allowed_origins: raise ValueError("Invalid origin")
Section sources
- fido2/server.py
- server/server/attestation.py
User presence is a critical security feature that ensures the user is physically interacting with the authenticator. The user presence flag (UP) indicates whether the authenticator detected user presence during the operation.
stateDiagram-v2
[*] --> UserInteraction
UserInteraction --> UserPresenceDetected : User touches device
UserInteraction --> NoUserPresence : No interaction
UserPresenceDetected --> UP_Flag_Set
NoUserPresence --> UP_Flag_NotSet
UP_Flag_Set --> ContinueProcessing
UP_Flag_NotSet --> UserPresenceError
UserPresenceError --> [*]
ContinueProcessing --> [*]
Diagram sources
- fido2/webauthn.py
When user verification is required but not performed:
# From fido2/server.py
if (
state["user_verification"] == UserVerificationRequirement.REQUIRED
and not auth_data.is_user_verified()
):
raise ValueError(
"User verification required, but user verified flag not set."
)# From fido2/webauthn.py
if not auth_data.is_user_present():
raise ValueError("User Present flag not set.")-
Check authenticator capabilities:
navigator.credentials.get({ publicKey: { userVerification: 'required' } }).then(assertion => { console.log('User verification enabled:', assertion.getClientExtensionResults()?.credProps?.uv); });
-
Validate user presence flags:
flags = auth_data_obj.flags user_present = bool(flags & AuthenticatorData.FLAG.UP) user_verified = bool(flags & AuthenticatorData.FLAG.UV) if not user_present: results["errors"].append("user_presence_not_set")
-
Test with different authenticators:
- Hardware security keys
- Platform authenticators (Touch ID, Face ID)
- Biometric authenticators
Section sources
- fido2/server.py
- fido2/webauthn.py
CBOR (Concise Binary Object Representation) is the primary encoding format used in WebAuthn for transmitting data between clients and servers. CBOR encoding/decoding issues can cause authentication failures at multiple levels.
graph TD
A["Raw Data"] --> B["CBOR Encoder"]
B --> C["Encoded CBOR"]
C --> D["Transmission"]
D --> E["CBOR Decoder"]
E --> F["Decoded Data"]
F --> G["Validation"]
H["Encoding Error"] --> B
I["Decoding Error"] --> E
J["Canonical Validation"] --> E
Diagram sources
- fido2/cbor.py
The server performs strict canonical CBOR validation:
# From fido2/ctap2/base.py
if self._strict_cbor:
expected = cbor.encode(decoded)
if expected != enc:
raise ValueError(
"Non-canonical CBOR from Authenticator.\n"
f"Got: {enc.hex()}\nExpected: {expected.hex()}"
)- Type mismatches: Incorrect data types in CBOR encoding
- Length inconsistencies: Problems with indefinite-length arrays/strings
- Tag validation: Invalid CBOR tags or tag values
- Float precision: Issues with floating-point encoding
# From fido2/cbor.py
def decode(data) -> CborType:
"""Decodes data from a CBOR-encoded byte string.
Also validates that no extra data follows the encoded object.
"""
value, rest = decode_from(data)
if rest != b"":
raise ValueError("Extraneous data")
return value// Inspect CBOR-encoded data in browser
function inspectCborData(encodedData) {
try {
const decoder = new CBORDecoder();
const decoded = decoder.decode(encodedData);
console.log('Decoded CBOR:', decoded);
return decoded;
} catch (error) {
console.error('CBOR decoding failed:', error);
return null;
}
}# From server/server/decoder/decode.py
def _parse_cbor_item(data: bytes, offset: int) -> Tuple[Dict[str, Any], int]:
if offset >= len(data):
raise _CborDecodingError("Unexpected end of CBOR data.", offset)
fb = data[offset]
major_type = fb >> 5
info = fb & 0b11111
# Handle different major types
if major_type == 0: # Unsigned integer
value, new_offset = load_int(info, data[offset+1:])
elif major_type == 1: # Negative integer
value, new_offset = load_nint(info, data[offset+1:])
# ... handle other major typesSection sources
- fido2/ctap2/base.py
- fido2/cbor.py
- server/server/decoder/decode.py
WebAuthn APIs return standardized error codes that help identify specific failure points in the authentication flow.
| Error Code | Description | Common Causes |
|---|---|---|
INVALID_STATE_ERR |
Operation not applicable to current state | Challenge expired, invalid state transitions |
NOT_SUPPORTED_ERR |
Requested operation not supported | Unsupported authenticator, disabled features |
SECURITY_ERR |
Security violation detected | Cross-origin requests, invalid signatures |
NOT_ALLOWED_ERR |
Operation not allowed | User cancellation, timeout exceeded |
UNKNOWN_ERROR |
Generic error occurred | Network issues, device failures |
The server implements custom error codes for specific failure scenarios:
// From server/server/static/scripts/advanced/json-editor.js
function validateAuthenticationPublicKey(publicKey) {
if (publicKey.timeout !== undefined) {
const timeoutValue = normalizeInteger(publicKey.timeout, 'publicKey.timeout');
if (timeoutValue !== null && timeoutValue < 0) {
throw new Error('publicKey.timeout must be zero or greater.');
}
}
if (publicKey.rpId !== undefined &&
(typeof publicKey.rpId !== 'string' || !publicKey.rpId.trim())) {
throw new Error('publicKey.rpId must be a non-empty string when provided.');
}
}// Example error response structure
{
"error": "Registration state not found or has expired. Please restart the registration process.",
"errorCode": "STATE_EXPIRED",
"timestamp": "2024-01-15T10:30:00Z"
}// Example authentication error response
{
"error": "User verification required, but user verified flag not set.",
"errorCode": "USER_VERIFICATION_REQUIRED",
"details": {
"userVerificationRequired": true,
"userVerified": false,
"authenticatorFlags": {
"userPresent": true,
"userVerified": false
}
}
}Section sources
- server/server/static/scripts/advanced/json-editor.js
Credential creation options define the parameters for generating new credentials. Validation failures in these options can prevent successful registration.
flowchart TD
A["Credential Options"] --> B["Algorithm Validation"]
A --> C["Attachment Validation"]
A --> D["User Verification Validation"]
A --> E["Resident Key Validation"]
B --> F["Supported Algorithms"]
C --> G["Platform/Cross-Platform"]
D --> H["Required/Preferred/None"]
E --> I["Required/Preferred/None"]
F --> J{"Validation Passed?"}
G --> J
H --> J
I --> J
J --> |No| K["Return Validation Error"]
J --> |Yes| L["Proceed with Registration"]
Diagram sources
- server/server/routes/advanced.py
# From server/server/routes/advanced.py
def _derive_algorithms_from_credentials(credentials: Iterable[Any]) -> List[PublicKeyCredentialParameters]:
"""Produce a list of allowed algorithms based on stored credential data."""
seen: Dict[int, PublicKeyCredentialParameters] = {}
for credential in credentials:
alg_value = _extract_credential_algorithm(credential)
if alg_value is None or alg_value in seen:
continue
seen[alg_value] = PublicKeyCredentialParameters(
type=PublicKeyCredentialType.PUBLIC_KEY,
alg=alg_value,
)
return list(seen.values())# From server/server/routes/advanced.py
allowed_attachments = request_allowed_attachments
if allowed_attachments:
response_attachment = normalize_attachment(
response.get('authenticatorAttachment')
)
if response_attachment not in allowed_attachments:
return jsonify({
"error": "Authenticator attachment is not permitted by the selected hints."
}), 400// From server/server/static/scripts/advanced/json-editor.js
function validateHints(hints, fieldName) {
if (!Array.isArray(hints)) {
throw new Error(`${fieldName} must be an array.`);
}
const validHints = ['platform', 'cross-platform'];
for (const hint of hints) {
if (!validHints.includes(hint)) {
throw new Error(`Invalid hint value: ${hint}`);
}
}
}function logCredentialOptions(options) {
console.log('Credential Options:', {
challenge: options.challenge,
rp: options.rp,
user: options.user,
pubKeyCredParams: options.pubKeyCredParams.map(param => ({
type: param.type,
alg: param.alg,
algorithmName: getAlgorithmName(param.alg)
})),
authenticatorSelection: options.authenticatorSelection,
timeout: options.timeout,
attestation: options.attestation
});
}function validateCredentialOptions(options) {
const errors = [];
// Check required fields
if (!options.challenge) errors.push('challenge is required');
if (!options.rp || !options.rp.id) errors.push('rp.id is required');
if (!options.user || !options.user.name) errors.push('user.name is required');
// Validate algorithm parameters
if (options.pubKeyCredParams) {
for (const param of options.pubKeyCredParams) {
if (!param.type || !param.alg) {
errors.push('Each pubKeyCredParam must have type and alg');
}
}
}
return errors;
}Section sources
- server/server/routes/advanced.py
- server/server/static/scripts/advanced/json-editor.js
Browser developer tools provide powerful capabilities for debugging WebAuthn authentication flows. This section covers systematic approaches to using these tools effectively.
graph LR
A["Browser DevTools"] --> B["Network Tab"]
A --> C["Console Tab"]
A --> D["Application Tab"]
A --> E["Security Tab"]
B --> F["Request/Response Payloads"]
C --> G["JavaScript Error Messages"]
D --> H["Session Storage"]
E --> I["Certificate Information"]
F --> J["Payload Analysis"]
G --> K["Error Tracing"]
H --> L["State Validation"]
I --> M["Security Validation"]
-
Enable network monitoring:
// Enable verbose logging for WebAuthn requests console.log('Starting WebAuthn request...');
-
Inspect request payloads:
async function debugWebAuthnRequest(endpoint, data) { console.log('Request to:', endpoint); console.log('Request body:', data); const response = await fetch(endpoint, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) }); console.log('Response status:', response.status); console.log('Response headers:', Object.fromEntries(response.headers.entries())); const responseBody = await response.json(); console.log('Response body:', responseBody); return responseBody; }
// Extract and log authentication response headers
function logAuthResponseHeaders(response) {
const headers = {};
for (const [key, value] of response.headers.entries()) {
headers[key] = value;
}
console.log('Response Headers:', {
'content-type': headers['content-type'],
'x-frame-options': headers['x-frame-options'],
'x-content-type-options': headers['x-content-type-options'],
'permissions-policy': headers['permissions-policy']
});
}// Enhanced error logging for WebAuthn operations
function logWebAuthnError(operation, error) {
console.group(`WebAuthn ${operation} Error`);
console.log('Error name:', error.name);
console.log('Error message:', error.message);
console.log('Error stack:', error.stack);
if (error.cause) {
console.log('Error cause:', error.cause);
}
if (error.webauthnCode) {
console.log('WebAuthn error code:', error.webauthnCode);
}
console.groupEnd();
}// Track authentication state changes
let authenticationState = {
challenge: null,
rpId: null,
userId: null,
timestamp: Date.now()
};
function updateAuthenticationState(newState) {
authenticationState = { ...authenticationState, ...newState };
console.log('Updated authentication state:', authenticationState);
}// Inspect session storage for authentication state
function inspectSessionStorage() {
const sessionStorageItems = {};
for (let i = 0; i < sessionStorage.length; i++) {
const key = sessionStorage.key(i);
try {
const value = sessionStorage.getItem(key);
sessionStorageItems[key] = JSON.parse(value);
} catch (e) {
sessionStorageItems[key] = value;
}
}
console.log('Session Storage:', sessionStorageItems);
}// Validate local storage for persistent authentication data
function validateLocalStorage() {
const localStorageItems = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith('webauthn_')) {
try {
const value = localStorage.getItem(key);
localStorageItems[key] = JSON.parse(value);
} catch (e) {
localStorageItems[key] = value;
}
}
}
console.log('WebAuthn Local Storage:', localStorageItems);
}// Monitor certificate validation in development
function monitorCertificateValidation() {
if (window.performance && performance.getEntriesByType) {
const resources = performance.getEntriesByType('resource');
const certificates = resources.filter(resource =>
resource.initiatorType === 'script' &&
resource.name.includes('webauthn')
);
console.log('Certificate Resources:', certificates);
}
}Section sources
- server/server/static/scripts/shared/auth-debug.js
User verification adds an additional security layer by requiring the user to authenticate through biometrics, PIN, or other verification methods.
flowchart TD
A["User Verification Request"] --> B{"Verification Method Available?"}
B --> |Yes| C["Perform Verification"]
B --> |No| D["Fallback Behavior"]
C --> E{"Verification Successful?"}
E --> |Yes| F["Set UV Flag"]
E --> |No| G["Return Verification Error"]
F --> H["Allow Authentication"]
G --> I["Deny Authentication"]
D --> J{"Required?"}
J --> |Yes| G
J --> |No| H
Diagram sources
- fido2/webauthn.py
# From fido2/webauthn.py
@unique
class UserVerificationRequirement(_StringEnum):
REQUIRED = "required"
PREFERRED = "preferred"
DISCOURAGED = "discouraged"Resident keys (discoverable credentials) allow storing credentials on the authenticator itself:
# From fido2/webauthn.py
@unique
class ResidentKeyRequirement(_StringEnum):
REQUIRED = "required"
PREFERRED = "preferred"
DISCOURAGED = "discouraged"// Determine authenticator capabilities
async function checkAuthenticatorCapabilities() {
const info = await navigator.credentials.get({
publicKey: {
challenge: new Uint8Array(32),
allowCredentials: [],
userVerification: 'required'
}
});
console.log('Authenticator capabilities:', {
residentKeys: info.getResidentKeys?.(),
userVerification: info.getUserVerification?.(),
extensions: info.getExtensions?.()
});
}// Validate user verification requirements
function validateUserVerificationRequirements(options) {
const requirements = {
required: false,
preferred: false,
discouraged: false
};
if (options.authenticatorSelection) {
const selection = options.authenticatorSelection;
if (selection.userVerification === 'required') {
requirements.required = true;
} else if (selection.userVerification === 'preferred') {
requirements.preferred = true;
} else if (selection.userVerification === 'discouraged') {
requirements.discouraged = true;
}
}
return requirements;
}// Monitor verification status during authentication
async function monitorVerificationStatus(assertion) {
const clientExtensions = assertion.getClientExtensionResults();
const credProps = clientExtensions?.credProps;
console.log('Verification status:', {
userVerified: credProps?.uv,
residentKey: credProps?.rk,
verificationMethod: credProps?.verificationMethod
});
}Section sources
- fido2/webauthn.py
- fido2/webauthn.py
Attestation statements provide evidence about the authenticator's characteristics and security properties. Different attestation formats require specific validation procedures.
graph TD
A["Attestation Object"] --> B["Format Detection"]
B --> C{"Format Type"}
C --> |packed| D["Packed Attestation"]
C --> |fido-u2f| E["U2F Attestation"]
C --> |android-key| F["Android Key Attestation"]
C --> |android-safetynet| G["Android SafetyNet Attestation"]
C --> |apple| H["Apple Attestation"]
C --> |tpm| I["TPM Attestation"]
C --> |none| J["Self Attestation"]
D --> K["Format-Specific Validation"]
E --> K
F --> K
G --> K
H --> K
I --> K
J --> K
K --> L["Root Certificate Validation"]
L --> M["Signature Verification"]
M --> N["Attestation Trust Evaluation"]
Diagram sources
- server/server/attestation.py
# From server/server/attestation.py
def _evaluate_classical_attestation_root(
attestation_object: AttestationObject,
attestation_result: Any,
client_data_hash: bytes,
verifier: Optional[Any],
now: datetime,
) -> Dict[str, Any]:
"""Evaluate attestation trust using classical x509 verification."""
trust_path = list(getattr(attestation_result, "trust_path", []) or [])
# Verify certificate chain
try:
verify_x509_chain(list(trust_path))
except InvalidSignature:
manual_chain_valid = False
except Exception as exc:
errors.append(f"certificate_chain_error: {exc}")
manual_chain_valid = False# From server/server/attestation.py
def _normalise_pqc_algorithm_identifier(value: Any) -> Optional[int]:
"""Return the COSE identifier for a PQC algorithm when discernible."""
if isinstance(value, int) and is_pqc_algorithm(value):
return value
if isinstance(value, str):
# Try to parse algorithm name or ID
try:
parsed = int(value, 10)
except ValueError:
parsed = None
if parsed is not None and is_pqc_algorithm(parsed):
return parsed
# Try to match algorithm name
lowered = value.strip().lower()
mapped = _PQC_ALGORITHM_NAME_TO_ID.get(lowered)
if mapped is not None:
return mapped// Log attestation details for debugging
function logAttestationDetails(attestationObject) {
console.log('Attestation Object:', {
format: attestationObject.format,
authData: attestationObject.authData,
attStmt: attestationObject.attStmt,
raw: Array.from(new Uint8Array(attestationObject.raw))
});
}// Validate attestation chain structure
function validateAttestationChain(attestationObject) {
const attStmt = attestationObject.attStmt;
const x5c = attStmt.x5c;
if (!x5c || !Array.isArray(x5c)) {
throw new Error('Attestation chain missing or invalid');
}
console.log('Attestation chain length:', x5c.length);
// Validate each certificate in the chain
x5c.forEach((cert, index) => {
try {
const decodedCert = decodeCertificate(cert);
console.log(`Certificate ${index}:`, {
subject: decodedCert.subject,
issuer: decodedCert.issuer,
validFrom: decodedCert.validFrom,
validTo: decodedCert.validTo
});
} catch (error) {
console.error(`Certificate ${index} validation failed:`, error);
}
});
}// Check if algorithm is supported
function isAlgorithmSupported(algorithmId) {
const supportedAlgorithms = [
-7, // ES256
-257, // RS256
-8, // EDDSA
// ... other supported algorithms
];
return supportedAlgorithms.includes(algorithmId);
}Section sources
- server/server/attestation.py
- server/server/attestation.py
Effective debugging of WebAuthn authentication flows requires a systematic approach that examines each layer of the authentication process.
flowchart TD
A["Authentication Failure"] --> B["Collect Error Information"]
B --> C["Analyze Client-Side Logs"]
C --> D["Examine Server Logs"]
D --> E["Validate Network Traffic"]
E --> F["Check Authenticator State"]
F --> G["Review Configuration"]
G --> H["Identify Root Cause"]
H --> I["Apply Fix"]
I --> J["Verify Resolution"]
# Enhanced logging for authentication flows
import logging
from datetime import datetime
def setup_authentication_logging():
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('authentication')
# Add correlation ID for request tracing
def log_authentication_event(event_type, request, response=None, error=None):
correlation_id = request.headers.get('X-Correlation-ID', 'unknown')
timestamp = datetime.utcnow().isoformat()
log_entry = {
'timestamp': timestamp,
'correlation_id': correlation_id,
'event_type': event_type,
'request_method': request.method,
'request_path': request.path,
'user_agent': request.headers.get('User-Agent'),
'remote_addr': request.remote_addr
}
if response:
log_entry['response_status'] = response.status_code
log_entry['response_time'] = response.elapsed.total_seconds()
if error:
log_entry['error'] = str(error)
log_entry['error_type'] = type(error).__name__
logger.debug(log_entry)// Comprehensive debugging utilities
class WebAuthnDebugger {
constructor() {
this.logs = [];
this.enabled = true;
}
enable() {
this.enabled = true;
console.log('WebAuthn debugging enabled');
}
disable() {
this.enabled = false;
console.log('WebAuthn debugging disabled');
}
log(level, message, data = null) {
if (!this.enabled) return;
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level,
message,
data: data ? this.sanitizeData(data) : null
};
this.logs.push(logEntry);
console[level.toLowerCase()](message, data);
}
sanitizeData(data) {
// Remove sensitive information
const sanitized = {...data};
delete sanitized.privateKey;
delete sanitized.secretKey;
delete sanitized.password;
return sanitized;
}
exportLogs() {
return JSON.stringify(this.logs, null, 2);
}
}
const debugger = new WebAuthnDebugger();// Implement graceful degradation for authentication failures
async function safeWebAuthnOperation(operation, fallback) {
try {
return await operation();
} catch (error) {
debugger.log('error', 'WebAuthn operation failed', error);
// Attempt fallback authentication
if (fallback) {
debugger.log('warn', 'Attempting fallback authentication');
return fallback();
}
throw error;
}
}// Implement retry logic for transient failures
async function retryWithBackoff(operation, maxRetries = 3, baseDelay = 1000) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
if (attempt < maxRetries) {
const delay = baseDelay * Math.pow(2, attempt - 1);
debugger.log('warn', `Attempt ${attempt} failed, retrying in ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
throw lastError;
}Section sources
- server/server/static/scripts/shared/auth-debug.js
Client-side failures often stem from JavaScript runtime problems, browser compatibility issues, or DOM manipulation errors.
// Comprehensive promise rejection handling
function handlePromiseRejection(promise, context) {
promise.catch(error => {
debugger.log('error', `Promise rejection in ${context}`, {
error: error.message,
stack: error.stack,
name: error.name
});
// Provide user-friendly error message
showErrorMessage(`Authentication failed: ${error.message}`);
});
}
// Example usage
handlePromiseRejection(
navigator.credentials.get(credentialRequestOptions),
'navigator.credentials.get'
);// Check for WebAuthn API availability
function checkWebAuthnSupport() {
const features = {
webauthn: typeof navigator?.credentials?.get === 'function',
subtleCrypto: typeof crypto?.subtle === 'object',
bufferSource: typeof ArrayBuffer !== 'undefined',
base64: typeof btoa === 'function'
};
if (!features.webauthn) {
debugger.log('error', 'WebAuthn API not supported');
return false;
}
if (!features.subtleCrypto) {
debugger.log('error', 'Subtle Crypto API not supported');
return false;
}
return true;
}// Robust event listener implementation
class WebAuthnEventHandler {
constructor(element) {
this.element = element;
this.listeners = new Map();
}
addEventListener(eventName, handler, options = {}) {
const wrappedHandler = (...args) => {
try {
return handler(...args);
} catch (error) {
debugger.log('error', `Event handler error (${eventName}):`, error);
// Prevent error propagation
if (options.preventDefault !== false) {
args[0]?.preventDefault();
}
}
};
this.element.addEventListener(eventName, wrappedHandler, options);
this.listeners.set(eventName, wrappedHandler);
}
removeEventListeners() {
for (const [eventName, handler] of this.listeners) {
this.element.removeEventListener(eventName, handler);
}
this.listeners.clear();
}
}// Enhanced form validation
function validateWebAuthnForm(form) {
const errors = [];
// Check required fields
const requiredFields = ['username', 'challenge'];
requiredFields.forEach(field => {
const input = form.querySelector(`[name="${field}"]`);
if (!input || !input.value.trim()) {
errors.push(`${field} is required`);
}
});
// Validate challenge format
const challengeInput = form.querySelector('[name="challenge"]');
if (challengeInput && challengeInput.value) {
try {
// Attempt to decode challenge
websafe_decode(challengeInput.value);
} catch (error) {
errors.push('Invalid challenge format');
}
}
return errors;
}Section sources
- server/server/static/scripts/advanced/json-editor.js
Server-side route handlers can fail due to various reasons including malformed requests, missing dependencies, or configuration errors.
# Comprehensive request validation
def validate_registration_request(request):
"""Validate incoming registration request."""
errors = []
# Check content type
if request.content_type != 'application/json':
errors.append('Content-Type must be application/json')
# Parse JSON
try:
data = request.get_json(silent=True)
if data is None:
errors.append('Invalid JSON in request body')
except Exception as e:
errors.append(f'Failed to parse JSON: {str(e)}')
data = {}
# Validate required fields
required_fields = ['challenge', 'rp', 'user']
for field in required_fields:
if field not in data:
errors.append(f'Missing required field: {field}')
return errors, data# Robust session state management
def manage_authentication_state(session, state_data):
"""Manage authentication state with proper validation."""
# Validate state structure
required_state_fields = ['challenge', 'challenge_expires', 'user_verification']
for field in required_state_fields:
if field not in state_data:
raise ValueError(f'Missing required state field: {field}')
# Check challenge expiration
challenge_expires = state_data.get('challenge_expires')
if challenge_expires and challenge_expires < datetime.utcnow():
session.pop('authentication_state', None)
raise ValueError('Challenge expired')
# Store validated state
session['authentication_state'] = state_data
return True# Secure cryptographic operations
def secure_cryptographic_operation(data, algorithm='sha256'):
"""Perform cryptographic operations with proper error handling."""
try:
# Validate input data
if not isinstance(data, (bytes, bytearray, memoryview)):
data = str(data).encode('utf-8')
# Perform hashing
hash_func = getattr(hashlib, algorithm, None)
if not hash_func:
raise ValueError(f'Unsupported hash algorithm: {algorithm}')
return hash_func(data).digest()
except Exception as e:
debugger.log('error', 'Cryptographic operation failed:', {
'error': str(e),
'algorithm': algorithm,
'data_type': type(data).__name__
})
raise# Reliable database operations
class DatabaseManager:
def __init__(self):
self.connection_pool = None
self.max_retries = 3
async def execute_query(self, query, params=None):
"""Execute database query with retry logic."""
for attempt in range(self.max_retries):
try:
if not self.connection_pool:
await self.initialize_connection_pool()
async with self.connection_pool.acquire() as connection:
result = await connection.fetch(query, *(params or []))
return result
except psycopg2.OperationalError as e:
if attempt < self.max_retries - 1:
wait_time = 2 ** attempt
await asyncio.sleep(wait_time)
continue
raise DatabaseConnectionError(f'Failed after {self.max_retries} attempts: {e}')
except Exception as e:
debugger.log('error', 'Database operation failed:', {
'attempt': attempt + 1,
'error': str(e),
'query': query[:100] # Log truncated query
})
raiseSection sources
- server/server/routes/simple.py
- server/server/routes/advanced.py
- Browser Support: Verify WebAuthn API availability
- HTTPS Requirement: Confirm secure context (HTTPS)
- Feature Flags: Check browser feature support
- Polyfills: Ensure appropriate polyfills are loaded
- RP ID Configuration: Verify relying party ID settings
- Origin Whitelist: Confirm allowed origins
- Challenge Generation: Test challenge creation
- Session Management: Validate session handling
- JavaScript Loading: Confirm script loading
- DOM Ready: Verify DOM readiness
- Event Handlers: Check event listener setup
- Form Validation: Test form validation
- Challenge Validation: Verify challenge matches
- Origin Verification: Confirm origin correctness
- User Presence: Check user interaction
- Attestation Format: Validate attestation type
- Assertion Creation: Verify assertion generation
- Signature Validation: Check signature verification
- Counter Validation: Confirm counter updates
- User Verification: Validate user verification
- Error Messages: Capture detailed error information
- Stack Traces: Collect complete stack traces
- Request/Response: Log complete request/response pairs
- Timing Information: Record operation timings
- Session State: Verify session integrity
- Database Records: Check database consistency
- Cache Entries: Validate cache state
- Log Entries: Review log completeness
- Packet Capture: Use Wireshark for network analysis
- Certificate Inspection: Verify certificate chains
- Timing Analysis: Measure operation latencies
- Rate Limiting: Check rate limiting effects
- Multiple Devices: Test with different authenticators
- Firmware Versions: Verify firmware compatibility
- Security Policies: Check security policy enforcement
- Error Conditions: Test error scenarios
- Resource Usage: Monitor CPU and memory usage
- Network Latency: Measure network delays
- Database Performance: Check database query times
- Cache Efficiency: Monitor cache hit rates
This comprehensive troubleshooting checklist provides a systematic approach to identifying and resolving authentication flow errors in WebAuthn implementations. Regular review and testing of these areas can significantly improve system reliability and user experience.