-
Notifications
You must be signed in to change notification settings - Fork 0
Simple Authentication Flows
- Introduction
- System Architecture
- Authentication Flow Overview
- Endpoint Specifications
- Challenge Generation and Verification
- Credential Registration Process
- Authentication Process
- Session Management
- Error Handling and Validation
- Performance Considerations
- Security Implementation
- Troubleshooting Guide
The simple authentication flows provide a streamlined implementation of WebAuthn authentication using the /register/begin, /register/complete, /authenticate/begin, and /authenticate/complete endpoints. These flows are designed for simplicity while maintaining strong security standards, supporting both classical and post-quantum cryptographic algorithms.
The implementation leverages the FIDO2 server library to handle the core WebAuthn protocol logic, including challenge generation, credential verification, and attestation validation. The system supports multiple cryptographic algorithms including ML-DSA variants for post-quantum security.
The simple authentication system follows a layered architecture with clear separation of concerns:
graph TB
subgraph "Client Layer"
Browser[Browser]
JS[auth-simple.js]
end
subgraph "Server Layer"
Flask[Flask Application]
Routes[simple.py Routes]
Config[Configuration]
end
subgraph "FIDO2 Layer"
FidoServer[FIDO2 Server]
Crypto[Cryptography Library]
end
subgraph "Storage Layer"
Memory[Session Storage]
Cloud[Cloud Storage]
end
Browser --> JS
JS --> Flask
Flask --> Routes
Routes --> FidoServer
FidoServer --> Crypto
Routes --> Memory
Routes --> Cloud
Diagram sources
- app.py
- simple.py
- config.py
Section sources
- app.py
- config.py
The simple authentication system implements two primary flows: credential registration and authentication. Both flows utilize stateless challenge validation with session-based state management.
sequenceDiagram
participant Client as Client Browser
participant Server as Simple Server
participant Fido2 as FIDO2 Server
participant Storage as Credential Storage
Note over Client,Storage : Registration Flow
Client->>Server : POST /register/begin
Server->>Fido2 : register_begin()
Fido2-->>Server : Challenge + State
Server-->>Client : Registration Options
Client->>Client : Generate Credential
Client->>Server : POST /register/complete
Server->>Fido2 : register_complete()
Fido2->>Fido2 : Verify Attestation
Fido2-->>Server : Auth Data
Server->>Storage : Store Credential
Server-->>Client : Registration Success
Note over Client,Storage : Authentication Flow
Client->>Server : POST /authenticate/begin
Server->>Storage : Load Credentials
Server->>Fido2 : authenticate_begin()
Fido2-->>Server : Challenge + State
Server-->>Client : Authentication Options
Client->>Client : Sign Assertion
Client->>Server : POST /authenticate/complete
Server->>Fido2 : authenticate_complete()
Fido2->>Fido2 : Verify Signature
Fido2-->>Server : Matched Credential
Server-->>Client : Authentication Success
Diagram sources
- simple.py
- simple.py
- auth-simple.js
- auth-simple.js
Endpoint: POST /api/register/begin?email={email}
Purpose: Initiates the credential registration process by generating challenge options for the client.
Request Schema:
{
"credentials": [
{
"credentialId": "base64_encoded_string",
"aaguid": "base64_encoded_string",
"publicKey": "base64_encoded_string",
"algorithm": -50
}
]
}Response Schema:
{
"publicKey": {
"challenge": "base64_url_encoded_string",
"rp": {
"id": "relying-party-id",
"name": "Relying Party Name"
},
"user": {
"id": "base64_url_encoded_string",
"name": "username",
"displayName": "Display Name"
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -50
}
],
"timeout": 90000,
"attestation": "none",
"authenticatorSelection": {
"authenticatorAttachment": "cross-platform",
"requireResidentKey": false,
"userVerification": "discouraged"
}
},
"__session_state": "base64_url_encoded_string"
}HTTP Methods: POST
Authentication: None required
Section sources
- simple.py
Endpoint: POST /api/register/complete?email={email}
Purpose: Completes the credential registration process by verifying the client's response and storing the credential.
Request Schema:
{
"id": "base64_url_encoded_string",
"rawId": "base64_url_encoded_string",
"response": {
"attestationObject": "base64_url_encoded_string",
"clientDataJSON": "base64_url_encoded_string"
},
"type": "public-key",
"__session_state": "base64_url_encoded_string"
}Response Schema:
{
"status": "OK",
"algo": "ML-DSA-87 (PQC)",
"storedCredential": {
"credentialId": "base64_encoded_string",
"email": "user@example.com",
"userName": "username",
"displayName": "Display Name",
"publicKey": "base64_encoded_string",
"algorithm": -50,
"signCount": 0,
"createdAt": 1640995200.0
},
"relyingParty": {
"attestationFmt": "packed",
"credentialId": "hex_string",
"rpIdHash": "hex_string",
"authenticatorDataHash": "hex_string"
}
}HTTP Methods: POST
Authentication: None required
Section sources
- simple.py
Endpoint: POST /api/authenticate/begin?email={email}
Purpose: Initiates the authentication process by loading stored credentials and generating challenge options.
Request Schema:
{
"credentials": [
{
"credentialId": "base64_encoded_string",
"aaguid": "base64_encoded_string",
"publicKey": "base64_encoded_string"
}
]
}Response Schema:
{
"publicKey": {
"challenge": "base64_url_encoded_string",
"rpId": "relying-party-id",
"allowCredentials": [
{
"id": "base64_url_encoded_string",
"type": "public-key"
}
],
"timeout": 90000,
"userVerification": "discouraged"
},
"__session_state": "base64_url_encoded_string"
}HTTP Methods: POST
Authentication: None required
Section sources
- simple.py
Endpoint: POST /api/authenticate/complete?email={email}
Purpose: Completes the authentication process by verifying the client's signature against the stored credential.
Request Schema:
{
"id": "base64_url_encoded_string",
"rawId": "base64_url_encoded_string",
"response": {
"authenticatorData": "base64_url_encoded_string",
"clientDataJSON": "base64_url_encoded_string",
"signature": "base64_url_encoded_string",
"userHandle": "base64_url_encoded_string"
},
"type": "public-key",
"__session_state": "base64_url_encoded_string"
}Response Schema:
{
"status": "OK",
"authenticatedCredentialId": "base64_url_encoded_string",
"signCount": 123
}HTTP Methods: POST
Authentication: None required
Section sources
- simple.py
The challenge generation process ensures cryptographic randomness and proper validation:
flowchart TD
Start([Begin Registration]) --> GenChallenge["Generate Random Challenge<br/>32 bytes minimum"]
GenChallenge --> ValidateChallenge{"Challenge Length >= 16 bytes?"}
ValidateChallenge --> |No| ThrowError["Throw ValueError"]
ValidateChallenge --> |Yes| CreateState["Create Internal State<br/>Store challenge + UV"]
CreateState --> BuildOptions["Build Credential Creation Options"]
BuildOptions --> SerializeJSON["Serialize to JSON<br/>with Session State"]
SerializeJSON --> SendToClient["Send to Client"]
Start2([Begin Authentication]) --> GenChallenge2["Generate Random Challenge<br/>32 bytes minimum"]
GenChallenge2 --> ValidateChallenge2{"Challenge Length >= 16 bytes?"}
ValidateChallenge2 --> |No| ThrowError2["Throw ValueError"]
ValidateChallenge2 --> |Yes| CreateState2["Create Internal State<br/>Store challenge + UV"]
CreateState2 --> BuildOptions2["Build Credential Request Options"]
BuildOptions2 --> SerializeJSON2["Serialize to JSON<br/>with Session State"]
SerializeJSON2 --> SendToClient2["Send to Client"]
Diagram sources
- server.py
- simple.py
The challenge verification process ensures that the client's response matches the originally issued challenge:
| Verification Step | Purpose | Implementation |
|---|---|---|
| Challenge Comparison | Verify client sent correct challenge | Constant-time comparison using constant_time.bytes_eq()
|
| Origin Validation | Ensure request comes from authorized origin |
verify_origin() function checks RP ID against origin |
| RP ID Hash Validation | Confirm authenticator data matches RP ID | Compare auth_data.rp_id_hash with expected hash |
| User Presence Flag | Verify user interaction occurred | Check auth_data.is_user_present()
|
| User Verification | Enforce user verification requirement | Validate UV flag against state requirement |
Section sources
- server.py
- server.py
The registration process involves multiple stages of validation and attestation:
sequenceDiagram
participant Client as Client
participant Server as Simple Server
participant Fido2 as FIDO2 Server
participant Attestation as Attestation Verifier
participant Storage as Storage
Client->>Server : POST /register/begin
Server->>Server : Parse existing credentials
Server->>Fido2 : register_begin()
Fido2-->>Server : Challenge + State
Server-->>Client : Registration Options
Client->>Client : Generate credential
Client->>Server : POST /register/complete
Server->>Server : Extract attestation details
Server->>Server : Validate session state
Server->>Fido2 : register_complete()
Fido2->>Attestation : Verify attestation
Attestation-->>Fido2 : Validation result
Fido2-->>Server : Authenticator data
Server->>Storage : Store credential
Server-->>Client : Registration success
Diagram sources
- simple.py
- simple.py
The system performs comprehensive attestation verification:
| Verification Type | Description | Validation Method |
|---|---|---|
| Signature Validity | Verify attestation signature | Cryptographic signature verification |
| Root Certificate Validity | Check certificate chain | Trust anchor validation |
| RP ID Hash | Verify relying party ID | SHA-256 hash comparison |
| AAGUID Matching | Validate authenticator ID | Authenticator-specific validation |
| Metadata Checks | Verify device metadata | MDS3 metadata validation |
Section sources
- simple.py
The system handles various public key formats and algorithms:
flowchart TD
ParseKey["Parse Public Key"] --> CheckFormat{"Key Format?"}
CheckFormat --> |COSE| DecodeCOSE["Decode COSE Format"]
CheckFormat --> |Base64| DecodeBase64["Decode Base64"]
CheckFormat --> |Hex| DecodeHex["Decode Hex"]
DecodeCOSE --> ValidateKey["Validate Key Parameters"]
DecodeBase64 --> ValidateKey
DecodeHex --> ValidateKey
ValidateKey --> CheckAlgorithm{"Algorithm Supported?"}
CheckAlgorithm --> |Yes| StoreKey["Store Public Key"]
CheckAlgorithm --> |No| RejectKey["Reject Unsupported Algorithm"]
StoreKey --> AddMaterial["Add to Public Key Material"]
AddMaterial --> Complete["Registration Complete"]
Diagram sources
- simple.py
Section sources
- simple.py
The authentication process validates client signatures against stored credentials:
sequenceDiagram
participant Client as Client
participant Server as Simple Server
participant Fido2 as FIDO2 Server
participant Crypto as Cryptography
Client->>Server : POST /authenticate/begin
Server->>Server : Load stored credentials
Server->>Fido2 : authenticate_begin()
Fido2-->>Server : Challenge + State
Server-->>Client : Authentication options
Client->>Client : Sign assertion
Client->>Server : POST /authenticate/complete
Server->>Server : Validate session state
Server->>Fido2 : authenticate_complete()
Fido2->>Crypto : Verify signature
Crypto-->>Fido2 : Verification result
Fido2-->>Server : Matched credential
Server-->>Client : Authentication success
Diagram sources
- simple.py
- server.py
The signature verification process ensures cryptographic authenticity:
| Step | Operation | Security Check |
|---|---|---|
| Credential Lookup | Find matching credential ID | O(n) linear search through stored credentials |
| Data Concatenation | Combine authenticator data + client hash | Prepare signed data for verification |
| Signature Verification | Verify against public key | Cryptographic signature validation |
| Counter Validation | Check signature counter | Prevent replay attacks |
| Flag Validation | Verify authenticator flags | Ensure proper authenticator state |
Section sources
- server.py
The simple authentication system uses Flask sessions for state management:
graph LR
subgraph "Session Storage"
State["Session State<br/>Challenge + UV"]
Credentials["Simple Credentials<br/>Stored in Session"]
RP["RP ID<br/>Registration/Auth"]
end
subgraph "Request Flow"
Begin["Begin Request"] --> Store["Store in Session"]
Complete["Complete Request"] --> Retrieve["Retrieve from Session"]
Retrieve --> Validate["Validate State"]
Validate --> Clear["Clear Session"]
end
Store --> State
Store --> Credentials
Store --> RP
State --> Begin
Credentials --> Begin
RP --> Begin
Diagram sources
- simple.py
- simple.py
| Security Feature | Implementation | Purpose |
|---|---|---|
| Session Expiration | Flask default session timeout | Prevent stale state attacks |
| State Binding | Challenge + UV in state | Bind state to specific request |
| Session Isolation | Separate registration/auth states | Prevent cross-session attacks |
| Secure Cookies | HTTPS-only session cookies | Protect session data |
Section sources
- simple.py
- simple.py
The system handles various error conditions with appropriate responses:
| Error Type | Cause | HTTP Status | Response |
|---|---|---|---|
| Invalid Challenge | Mismatched challenge | 400 | "Wrong challenge in response" |
| Expired State | Stale session state | 400 | "State not found or expired" |
| Invalid Signature | Malformed signature | 400 | "Invalid signature" |
| Unknown Credential | Non-existent credential ID | 400 | "Unknown credential ID" |
| Missing Credentials | No stored credentials | 404 | "No credentials found" |
| Invalid Origin | Unauthorized origin | 400 | "Invalid origin" |
flowchart TD
ReceiveRequest["Receive Request"] --> ValidateJSON{"Valid JSON?"}
ValidateJSON --> |No| JSONError["Return 400 JSON Error"]
ValidateJSON --> |Yes| CheckState{"State Available?"}
CheckState --> |No| StateError["Return 400 State Error"]
CheckState --> |Yes| ValidateChallenge["Validate Challenge"]
ValidateChallenge --> ChallengeValid{"Challenge Valid?"}
ChallengeValid --> |No| ChallengeError["Return 400 Challenge Error"]
ChallengeValid --> |Yes| ValidateOrigin["Validate Origin"]
ValidateOrigin --> OriginValid{"Origin Valid?"}
OriginValid --> |No| OriginError["Return 400 Origin Error"]
OriginValid --> |Yes| ProcessRequest["Process Request"]
ProcessRequest --> Success["Return Success"]
Diagram sources
- simple.py
- simple.py
Section sources
- simple.py
- simple.py
The system implements efficient stateless challenge validation:
| Optimization Technique | Implementation | Benefit |
|---|---|---|
| Constant-Time Comparisons | constant_time.bytes_eq() |
Prevent timing attacks |
| Lazy Credential Loading | Load credentials only when needed | Reduce memory usage |
| Efficient JSON Serialization |
make_json_safe() function |
Minimize serialization overhead |
| Session Cleanup | Automatic state clearing | Prevent memory leaks |
While the system is primarily stateless, several caching opportunities exist:
graph TB
subgraph "Caching Opportunities"
PublicKey["Public Key Material<br/>Cached in Memory"]
Metadata["Device Metadata<br/>Cached Locally"]
Certificates["Certificate Chains<br/>Cached Temporarily"]
end
subgraph "Storage Backends"
Memory["In-Memory Cache"]
LocalFS["Local File System"]
Cloud["Cloud Storage"]
end
PublicKey --> Memory
Metadata --> LocalFS
Certificates --> Memory
Memory --> Cloud
Diagram sources
- simple.py
The system supports multiple cryptographic algorithms with performance considerations:
| Algorithm Family | Performance | Security Level | Use Case |
|---|---|---|---|
| ML-DSA-87 | Fast | Post-Quantum | High-security applications |
| ML-DSA-65 | Medium | Post-Quantum | Balanced performance/security |
| ML-DSA-44 | Fast | Post-Quantum | Resource-constrained devices |
| ES256 | Very Fast | Classical | Legacy compatibility |
| RS256 | Medium | Classical | Enterprise environments |
Section sources
- simple.py
The simple authentication system implements comprehensive security measures:
graph TB
subgraph "Security Layers"
Crypto["Cryptographic Validation"]
Auth["Authentication"]
Integrity["Data Integrity"]
Replay["Replay Protection"]
end
subgraph "Validation Checks"
SigVerify["Signature Verification"]
CounterCheck["Signature Counter"]
ChallengeVal["Challenge Validation"]
OriginCheck["Origin Validation"]
end
subgraph "Protection Mechanisms"
TimingAttacks["Timing Attack Prevention"]
StateBinding["State Binding"]
SessionIsolation["Session Isolation"]
ErrorHandling["Secure Error Handling"]
end
Crypto --> SigVerify
Crypto --> CounterCheck
Auth --> ChallengeVal
Auth --> OriginCheck
Integrity --> TimingAttacks
Replay --> StateBinding
Replay --> SessionIsolation
Replay --> ErrorHandling
Diagram sources
- server.py
- server.py
| Threat Category | Mitigation Strategy | Implementation |
|---|---|---|
| Replay Attacks | Signature counters | AuthenticatorData counter validation |
| Timing Attacks | Constant-time operations |
constant_time.bytes_eq() usage |
| State Tampering | Challenge binding | Unique challenges per request |
| Credential Theft | Multi-factor support | User verification requirements |
| Man-in-the-Middle | Origin validation | RP ID hash verification |
Section sources
- server.py
- server.py
Issue: "Registration state not found or has expired"
- Cause: Session timeout or state corruption
- Solution: Restart the registration process
- Prevention: Ensure quick completion of registration flow
Issue: "Invalid signature"
- Cause: Malformed attestation or corrupted data
- Solution: Check client-side implementation and retry
- Debugging: Enable detailed logging for attestation verification
Issue: "Unknown credential ID"
- Cause: Credential not found in storage
- Solution: Verify credential was properly registered
- Prevention: Implement credential backup and recovery
Issue: "Wrong challenge in response"
- Cause: Challenge replay or state mismatch
- Solution: Clear browser cache and restart authentication
- Debugging: Check challenge generation and validation logic
Issue: Slow challenge generation
- Cause: Insufficient entropy source
- Solution: Ensure system has adequate entropy
- Monitoring: Track challenge generation timing
Issue: Memory usage growth
- Cause: Session state accumulation
- Solution: Implement session cleanup policies
- Monitoring: Track session memory usage
The system provides comprehensive debug information:
{
"debugInfo": {
"attestationFormat": "packed",
"algorithmsUsed": [-50],
"rpIdHashValid": true,
"rpIdHash": "expected_hash",
"rpIdHashExpected": "expected_hash",
"attestationSummary": {
"signatureValid": true,
"rootValid": true,
"rpIdHashValid": true,
"aaguidMatch": true
}
}
}Section sources
- simple.py
- simple.py