A secure authentication microservice implementing passwordless WebAuthn (TouchID/biometric) authentication with JWT tokens and role-based access control. Built for high-security environments with comprehensive logging and authorization controls.
✨ Interactive Demo Frontend - Test TouchID authentication in your browser at http://localhost:8000
- Microservice Architecture Goals
- Threat Model
- Security Controls
- Endpoint Implementations
- Role-Based Access Control
- Quick Start
- Database Schema
- Configuration
- Development & Testing
This authentication microservice is designed to be:
- Stateless: JWT-based authentication eliminates server-side session storage
- API-First: RESTful JSON API suitable for integration with any frontend
- Phishing-Resistant: WebAuthn prevents credential theft and phishing attacks
- Auditable: Comprehensive logging supports security monitoring and compliance
- Scalable: No session state allows horizontal scaling
- Microservice-Ready: Designed to run in containerized environments with external infrastructure support (load balancers, secret managers, monitoring)
- Defense in Depth: Authentication (WebAuthn) + Authorization (JWT + RBAC)
- Least Privilege: Users can only access their own data; admin role for cross-user access
- Security by Default: Secure configurations, comprehensive logging, input validation
- Operational Visibility: Dual logging (file + database) for security team monitoring
This microservice is designed for deployment in a high-security environment with:
- Infrastructure-Layer Security: TLS termination at load balancer/reverse proxy
- Network Security: WAF, DDoS protection, and rate limiting at infrastructure layer
- Secrets Management: External secret manager (Vault, AWS Secrets Manager, Azure Key Vault)
- Centralized Logging: Integration with SIEM/log aggregation (ELK, Splunk, CloudWatch)
- Internal Deployment: Not directly exposed to the public internet
| Threat | Attack Vector | Mitigation |
|---|---|---|
| Credential Theft | Password database breach | No passwords stored (WebAuthn only) |
| Phishing | Fake login page | WebAuthn origin binding prevents credential use on wrong domain |
| Replay Attacks | Intercepted authentication | Challenge-response protocol with cryptographic nonces |
| Session Hijacking | Stolen JWT token | Short expiration (60 min), no refresh tokens in this version |
| Credential Stuffing | Reused passwords | No passwords (WebAuthn eliminates this attack) |
| MITM Attacks | Man-in-the-middle | HTTPS required in production (enforced by WebAuthn spec) |
| Unauthorized Access | Accessing other users' data | RBAC with ownership checks on every protected endpoint |
| Credential Cloning | Duplicating biometric credentials | Sign count validation detects cloned credentials |
| Account Enumeration | Discovering valid emails | Generic error messages prevent user enumeration |
| Insider Threats | Malicious admin access | Comprehensive audit logging of all access attempts |
The microservice provides comprehensive monitoring capabilities:
- File-based logs (
app.log): Immediate visibility during development/debugging - Database audit trail (
audit_logstable): Queryable history for analysis
- User registration attempts (with IP, user agent, success/failure)
- Login attempts (with IP, user agent, success/failure)
- Authorization failures (users attempting to access others' data)
- JWT validation failures
- Credential verification failures
# Find failed login attempts (potential brute force)
sqlite3 -header -column app.db "SELECT created_at, user_email, ip_address, user_agent, details
FROM audit_logs
WHERE event_type='login_complete' AND success=0
ORDER BY created_at DESC;"
# Detect account compromise (multiple IPs for same user)
sqlite3 -header -column app.db "SELECT user_email, ip_address, COUNT(*) as attempts
FROM audit_logs
WHERE event_type='login_complete' AND success=1
GROUP BY user_email, ip_address
HAVING COUNT(*) > 1;"
# Track unauthorized access attempts
sqlite3 -header -column app.db "SELECT created_at, user_email, ip_address, details
FROM audit_logs
WHERE event_type='user_access' AND success=0
ORDER BY created_at DESC;"- SOC 2: Audit trail of all authentication/authorization events
- HIPAA: Comprehensive logging of PHI access (when storing health data)
- GDPR: User activity tracking for right-to-access requests
- WebAuthn with Platform Authenticators: TouchID, FaceID, Windows Hello
- Multi-Factor by Design: Something you have (device) + something you are (biometric)
- Public Key Cryptography: Private key never leaves device
- Challenge-Response Protocol: Prevents replay attacks with cryptographic nonces
- Sign Count Validation: Detects and prevents credential cloning
- JWT Token-Based Access Control: Stateless authentication
- Token Expiration: 60-minute lifetime (configurable)
- Role-Based Access Control (RBAC):
userrole: Can only access own dataadminrole: Can access any user's data
- Per-Endpoint Authorization Checks: Validates ownership or admin role
- JWT Claims:
sub(user_id),email,first_name,last_name,role
- Pydantic Models: Type-safe validation for all inputs
- Email Format Validation: RFC-compliant email validation
- Required Field Validation: Enforced at API layer
- Type Checking: Prevents type confusion attacks
- SQL Injection Prevention: SQLAlchemy ORM parameterized queries
- Email Uniqueness Constraints: Prevents duplicate accounts
- Foreign Key Relationships: Ensures referential integrity
- Indexed Lookups: Optimized queries on
email,event_type,created_at
- Dual Logging System:
- File-based (
app.log): Real-time debugging - Database (
audit_logs): Long-term audit trail
- File-based (
- IP Address Tracking: All events logged with client IP (handles
X-Forwarded-For) - User Agent Tracking: Browser/client identification
- Success/Failure Recording: All authentication/authorization outcomes logged
- Generic Error Messages: Prevents user enumeration
- Generic Error Messages: "Invalid credentials" (no hints about user existence)
- Proper HTTP Status Codes:
401(unauthenticated),403(unauthorized),404(not found) - Challenge Cleanup: Failed attempts clean up server-side state
The following controls are intentionally deferred due to infrastructure requirements or scope limitations:
- Why Deferred: Requires distributed cache (Redis) for multi-instance deployments
- Risk: Brute force attacks possible on registration/login endpoints
- Production Recommendation: Implement with
slowapi+ Redis or infrastructure-level WAF rate limiting - Mitigation: WebAuthn's biometric requirement slows brute force significantly
- Why Deferred: Local development uses HTTP
- Risk: Credentials visible to network observers
- Production Requirement: WebAuthn spec requires HTTPS in production
- Production Recommendation: TLS termination at reverse proxy (nginx, ALB) with Let's Encrypt certificates
- Why Deferred: Requires email service integration (SendGrid, AWS SES)
- Risk: Users can register with emails they don't own
- Production Recommendation: Send verification email with time-limited token
- Workaround: Manual admin verification of user accounts
- Why Deferred: Complex multi-step process requiring email service
- Risk: Users who lose device credentials cannot recover access
- Current Mitigation: Users must re-register with new email
- Production Recommendation: Backup authentication methods (recovery codes, secondary device)
- Why Deferred: Currently using in-memory dictionary
- Risk: Server restart invalidates pending registrations/logins
- Current Mitigation: Acceptable for development; users retry registration
- Production Recommendation: Redis with TTL (5-minute expiration)
- Why Deferred: Using environment variables with fallback defaults
- Risk: JWT secret could be exposed in source code or logs
- Current Mitigation: Documentation requires
JWT_SECRETin production - Production Requirement: HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault
- Why Deferred: Currently logging to local file and SQLite
- Risk: Logs not centralized, difficult to analyze at scale, can be lost
- Current Mitigation: Dual logging (file + database) for redundancy
- Production Recommendation: ELK stack, Splunk, CloudWatch, or similar SIEM
- Why Deferred: Requires infrastructure (CloudFlare, AWS WAF, Azure Front Door)
- Risk: Application vulnerable to volumetric attacks
- Current Mitigation: None at application level
- Production Requirement: Deploy behind WAF with DDoS protection
- Why Deferred: SQLite doesn't support native encryption
- Risk: Database file contains user PII in plaintext on disk
- Current Mitigation: File system permissions restrict access
- Production Recommendation: PostgreSQL with encryption or encrypted filesystem (LUKS, BitLocker)
- Why Deferred: Adds complexity with token rotation logic
- Risk: Users must re-authenticate every 60 minutes
- Current Mitigation: Short session timeout acceptable for high-security applications
- Production Recommendation: Implement refresh token flow with rotation and family tracking
- Why Deferred: No specific frontend domain defined
- Risk: Any origin can call the API in browser context
- Current Mitigation: Accept for development; WebAuthn origin binding provides some protection
- Production Requirement: Configure specific allowed origins in CORS middleware
The original specification requested:
POST /users- User registrationPOST /login- User loginGET /users/{id}- Retrieve user information
Our implementation modifies registration and login to accommodate WebAuthn's cryptographic protocol, which inherently requires two round-trips between client and server.
WebAuthn is a challenge-response protocol that cannot be collapsed into a single request-response cycle:
- Server must generate a cryptographic challenge (random nonce)
- Client must sign the challenge with the authenticator (TouchID/FaceID)
- Browser APIs are asynchronous and require user interaction (biometric prompt)
- Signed result must be sent back for verification
This protocol design prevents replay attacks and ensures the user is physically present with the registered device.
| Original Spec Operation | Our Implementation | Semantic Equivalence |
|---|---|---|
POST /users (register) |
POST /users (307 redirect) → POST /register/begin → POST /register/complete |
Two-step registration creates user |
POST /login |
POST /login (307 redirect) → POST /login/begin → POST /login/complete |
Two-step login returns JWT |
GET /users/{id} |
GET /users/{id} |
Unchanged - works as specified |
Spec Compatibility Notes:
POST /usersandPOST /loginreturn307 Temporary Redirectto preserve the POST method and request body- Clients that follow redirects (most HTTP libraries) work seamlessly with the original spec endpoints
- The redirects maintain API compatibility while supporting WebAuthn's required two-step protocol
The original requirements are fully satisfied: users can register, login, and retrieve information. The only difference is that registration and login require two sequential API calls due to the cryptographic protocol.
Users self-register with First Name, Last Name, Email, Date of Birth, and Job Title. WebAuthn creates a phishing-resistant credential tied to the user's device biometrics.
Note: For spec compatibility, POST /users is available as a redirect endpoint. It returns a 307 Temporary Redirect to /register/begin, preserving the POST method and request body. Clients that follow redirects will automatically be routed to the correct endpoint.
Initiates registration by sending user information. Server generates a cryptographic challenge.
Request:
{
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"date_of_birth": "1990-01-15T00:00:00Z",
"job_title": "Software Engineer"
}Response: WebAuthn PublicKeyCredentialCreationOptions
{
"publicKey": {
"challenge": "base64url-encoded-challenge",
"rp": {
"id": "localhost",
"name": "Auth Example"
},
"user": {
"id": "base64url-encoded-user-uuid",
"name": "jane@example.com",
"displayName": "Jane Doe"
},
"pubKeyCredParams": [
{"type": "public-key", "alg": -7},
{"type": "public-key", "alg": -257}
],
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"userVerification": "required"
},
"timeout": 60000
}
}Implementation Details:
- Creates user record with
role: "user"(default) - Generates UUID for
user.id - Stores challenge in-memory (5-minute expiration)
- Logs registration initiation with IP address and user agent
- Returns WebAuthn options for
navigator.credentials.create()
Security Controls:
- Email uniqueness validation (prevents duplicate accounts)
- Input validation via Pydantic models
- Audit logging of registration attempts
- Generic error messages (prevents user enumeration)
Completes registration by submitting the WebAuthn credential created by the user's authenticator.
Request:
{
"email": "jane@example.com",
"credential": {
"id": "credential-id-base64url",
"rawId": "credential-id-base64url",
"response": {
"clientDataJSON": "base64url-encoded-json",
"attestationObject": "base64url-encoded-cbor"
},
"type": "public-key"
}
}Response (201 Created):
{
"id": "user-uuid",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"date_of_birth": "1990-01-15T00:00:00Z",
"job_title": "Software Engineer",
"role": "user",
"created_at": "2025-10-22T12:00:00Z"
}Implementation Details:
- Verifies credential signature using
webauthnlibrary - Validates challenge matches stored challenge
- Stores credential public key in
webauthn_credentialstable - Deletes challenge from memory after use
- Logs successful registration with IP address and user agent
- Returns complete user object (excluding sensitive credential data)
Security Controls:
- Cryptographic verification of attestation
- Challenge validation (prevents replay attacks)
- Sign count initialization (detects credential cloning)
- One-time challenge use (deleted after verification)
- Credential uniqueness constraint in database
Login flow uses WebAuthn challenge-response instead of external authentication services like Auth0. Returns a JWT token containing user claims and role.
Note: For spec compatibility, POST /login is available as a redirect endpoint. It returns a 307 Temporary Redirect to /login/begin, preserving the POST method and request body. Clients that follow redirects will automatically be routed to the correct endpoint.
Initiates login by providing email. Server generates challenge and returns allowed credentials.
Request:
{
"email": "jane@example.com"
}Response: WebAuthn PublicKeyCredentialRequestOptions
{
"publicKey": {
"challenge": "base64url-encoded-challenge",
"rpId": "localhost",
"allowCredentials": [
{
"type": "public-key",
"id": "credential-id-base64url"
}
],
"userVerification": "required",
"timeout": 60000
}
}Implementation Details:
- Looks up user by email
- Retrieves all registered credentials for user
- Generates fresh challenge (stored in-memory)
- Logs login initiation with IP address
- Returns challenge + allowed credential IDs for
navigator.credentials.get()
Security Controls:
- Generic error if user not found (prevents user enumeration: "Invalid credentials")
- Fresh challenge per login attempt
- All registered credentials allowed (supports multiple devices)
- Audit logging of login attempts
Completes login by submitting the WebAuthn assertion (signed challenge). Returns JWT token.
Request:
{
"email": "jane@example.com",
"assertion": {
"id": "credential-id-base64url",
"rawId": "credential-id-base64url",
"response": {
"clientDataJSON": "base64url-encoded-json",
"authenticatorData": "base64url-encoded-data",
"signature": "base64url-encoded-signature"
},
"type": "public-key"
}
}Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"user": {
"id": "user-uuid",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"role": "user"
}
}Implementation Details:
- Verifies signature using stored public key
- Validates challenge matches expected challenge
- Validates sign count (must be greater than stored count)
- Updates stored sign count (prevents credential cloning)
- Generates JWT token with claims:
sub,email,first_name,last_name,role - Logs successful login with IP address and user agent
- Deletes challenge from memory after use
JWT Token Claims:
{
"sub": "user-uuid",
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"role": "user",
"exp": 1729608000,
"iat": 1729604400
}Security Controls:
- Cryptographic signature verification
- Sign count validation (detects cloned credentials)
- Challenge validation (prevents replay attacks)
- Short token expiration (60 minutes, configurable)
- HS256 signing algorithm with secret key
- Generic error messages (prevents user enumeration)
Why Not Auth0:
- WebAuthn is the primary authentication mechanism (Auth0 would be redundant)
- Self-contained microservice with no external dependencies
- JWT generation is simple and secure with proper key management
- Avoids vendor lock-in and external service costs
Protected endpoint that returns user information. Requires JWT authentication. Implements role-based access control.
Headers:
Authorization: Bearer <jwt-token>
Response:
{
"id": "user-uuid",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"date_of_birth": "1990-01-15T00:00:00Z",
"job_title": "Software Engineer",
"role": "user",
"created_at": "2025-10-22T12:00:00Z"
}Implementation Details:
- Validates JWT token (signature, expiration)
- Extracts user identity from JWT claims (
sub,role) - Checks authorization:
- Users with
userrole: Can only access their own profile (user_id == requested_id) - Users with
adminrole: Can access any user's profile
- Users with
- Logs access attempt with IP address, user agent, and authorization outcome
- Returns user object if authorized
Authorization Logic:
is_own_profile = current_user.id == user_id
is_admin = current_user.role == "admin"
if not is_own_profile and not is_admin:
# Deny access, log authorization failure
raise HTTPException(status_code=403, detail="You don't have permission to access this resource")Security Controls:
- JWT validation (signature, expiration)
- Role-based access control (RBAC)
- Per-request authorization check
- Audit logging of access attempts (both success and failure)
- IP address and user agent tracking
- Generic error messages
Error Responses:
401 Unauthorized: Invalid or expired JWT token403 Forbidden: User trying to access another user's data (non-admin)404 Not Found: User doesn't exist
Audit Logging:
- Success: "User Jane Doe accessed by Jane Doe (role: user) from IP: 192.168.1.1"
- Failure (non-admin accessing others' data): "Authorization failed: User jane@example.com (role: user) attempted to access user abc-123's data from IP: 192.168.1.1"
| Role | Permissions | Use Case |
|---|---|---|
user |
Access own data only | Default role for all registered users |
admin |
Access any user's data | Support staff, security team, auditors |
- Default: All users registered via
POST /register/beginreceiverole: "user" - Admin Assignment: Currently requires direct database update (no API endpoint for role promotion)
UPDATE users SET role='admin' WHERE email='admin@example.com';
- Future Enhancement: Implement
POST /admin/users/{id}/promoteendpoint (requires authentication as existing admin)
The role claim is embedded in the JWT token during login:
{
"sub": "user-uuid",
"email": "admin@example.com",
"first_name": "Admin",
"last_name": "User",
"role": "admin",
"exp": 1729608000,
"iat": 1729604400
}Authorization is enforced at the endpoint level using dependency injection:
def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
# Validates JWT and extracts user from database
# Raises 401 if invalid
return user
@app.get("/users/{user_id}")
def get_user(user_id: str, current_user: User = Depends(get_current_user), ...):
# Check authorization based on role
is_own_profile = current_user.id == user_id
is_admin = current_user.role == "admin"
if not is_own_profile and not is_admin:
# Log and deny access
raise HTTPException(status_code=403, ...)All admin access is logged with full context:
SELECT * FROM audit_logs WHERE user_email='admin@example.com' AND event_type='user_access';Example log entry:
event_type: user_access
user_email: admin@example.com
user_id: admin-uuid
ip_address: 192.168.1.1
success: 1
details: User John Doe accessed by Admin User (role: admin)
- Python 3.9+
- Modern browser with WebAuthn support (Chrome, Safari, Firefox, Edge)
- Biometric hardware (TouchID, FaceID, Windows Hello) or security key
# Clone and navigate to repository
cd auth-example
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Set JWT secret (production requirement)
export JWT_SECRET="your-secure-random-secret-key"
# Run the server
uvicorn main:app --reloadThe server starts at http://localhost:8000.
- Open browser:
http://localhost:8000 - Register: Fill form → Click "Register with Biometrics" → Authenticate with TouchID
- View profile: See your user data and JWT expiration countdown
- Logout and login: Test the login flow with biometrics
FastAPI automatic docs: http://localhost:8000/docs
This guide shows how to register a user with the frontend, obtain a JWT token, and use it to test the protected GET /users/{id} endpoint in the FastAPI documentation interface.
- Open the frontend: Navigate to
http://localhost:8000 - Fill out the registration form:
- First Name:
Jane - Last Name:
Doe - Email:
jane@example.com - Date of Birth:
1990-01-15 - Job Title:
Software Engineer
- First Name:
- Click "Register with Biometrics"
- Authenticate with TouchID/FaceID when prompted
- Note your user ID: After registration, you'll be automatically logged in and see your profile. The browser console will show your user ID, or you can find it in the browser's developer tools → Application → Session Storage →
webauthn_user_id
Option A: From Browser Session Storage
- Open browser developer tools (F12 or right-click → Inspect)
- Go to Application tab → Session Storage →
http://localhost:8000 - Find the key
webauthn_jwt - Copy the token value (starts with
eyJ...)
Option B: From Browser Console
- Open browser console (F12 → Console tab)
- Type:
sessionStorage.getItem('webauthn_jwt') - Copy the token value (without quotes)
Option C: Login and Extract Token If you've logged out, login again and extract the token:
- Click "Login" tab
- Enter your email:
jane@example.com - Click "Login with Biometrics"
- Authenticate with TouchID/FaceID
- Extract token using Option A or B above
- Open API docs: Navigate to
http://localhost:8000/docs - Find GET /users/{id}: Scroll down to the "Protected Endpoints" section
- Click "Try it out"
- Authorize with your JWT:
- Click the "Authorize" button (lock icon) at the top right
- In the "Value" field, paste your JWT token
- Click "Authorize"
- Click "Close"
- Test the endpoint:
- Enter your
user_idin theuser_idfield - Click "Execute"
- Enter your
- View the response:
- You should see a
200 OKresponse with your user data including:id,first_name,last_name,email,date_of_birth,job_title,role,created_at
- You should see a
Expected Response:
{
"id": "your-user-uuid",
"first_name": "Jane",
"last_name": "Doe",
"email": "jane@example.com",
"date_of_birth": "1990-01-15T00:00:00",
"job_title": "Software Engineer",
"role": "user",
"created_at": "2025-10-22T12:00:00"
}Test 1: Try to access another user's data (should fail)
- Register a second user with a different email
- Note the second user's ID
- In FastAPI docs, try to GET the second user's ID using the first user's JWT
- Expected:
403 Forbidden- "You don't have permission to access this resource"
Test 2: Test with expired/invalid token
- In FastAPI docs, click "Authorize"
- Enter an invalid token (e.g.,
invalid-token) - Try to GET any user ID
- Expected:
401 Unauthorized- "Could not validate credentials"
Test 3: Test without token
- In FastAPI docs, click "Authorize" then "Logout"
- Try to GET any user ID
- Expected:
401 Unauthorized- "Not authenticated"
To test the admin role functionality:
-
Promote a user to admin:
sqlite3 app.db "UPDATE users SET role='admin' WHERE email='jane@example.com';" -
Login again to get a new JWT with the
adminrole claim:- Logout from the frontend
- Login again with TouchID
- Extract the new JWT token
-
Test admin access:
- In FastAPI docs, authorize with the new admin JWT
- Try to GET another user's data
- Expected:
200 OKwith the other user's data (admins can access any user)
-
Verify audit logs:
sqlite3 app.db "SELECT created_at, user_email, details FROM audit_logs WHERE event_type='user_access' ORDER BY created_at DESC LIMIT 5;"You should see log entries showing the admin accessing other users' data.
After registration, you can find user IDs in the database:
sqlite3 app.db "SELECT id, email, first_name, last_name, role FROM users;"CREATE TABLE users (
id TEXT PRIMARY KEY, -- UUID
first_name TEXT NOT NULL, -- First name
last_name TEXT NOT NULL, -- Last name
email TEXT UNIQUE NOT NULL, -- Email (indexed for fast lookup)
date_of_birth DATETIME NOT NULL, -- Date of birth
job_title TEXT NOT NULL, -- Job title
role TEXT NOT NULL DEFAULT 'user', -- Role: 'user' or 'admin'
created_at DATETIME NOT NULL -- Registration timestamp
);
CREATE INDEX idx_users_email ON users(email);CREATE TABLE webauthn_credentials (
id TEXT PRIMARY KEY, -- UUID
user_id TEXT NOT NULL, -- Foreign key to users.id
credential_id BLOB UNIQUE NOT NULL, -- WebAuthn credential ID (binary)
public_key BLOB NOT NULL, -- Public key for signature verification
sign_count INTEGER NOT NULL DEFAULT 0, -- Counter for credential cloning detection
created_at DATETIME NOT NULL, -- Credential creation timestamp
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE INDEX idx_credentials_user_id ON webauthn_credentials(user_id);
CREATE UNIQUE INDEX idx_credentials_credential_id ON webauthn_credentials(credential_id);CREATE TABLE audit_logs (
id TEXT PRIMARY KEY, -- UUID
event_type TEXT NOT NULL, -- Event category (indexed)
user_email TEXT, -- Email address (indexed)
user_id TEXT, -- User ID (foreign key)
ip_address TEXT, -- Client IP address
user_agent TEXT, -- Browser/client user agent
success INTEGER NOT NULL, -- 1 for success, 0 for failure
details TEXT, -- Additional context
created_at DATETIME NOT NULL, -- Event timestamp (indexed)
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE INDEX idx_audit_event_type ON audit_logs(event_type);
CREATE INDEX idx_audit_user_email ON audit_logs(user_email);
CREATE INDEX idx_audit_created_at ON audit_logs(created_at);Event Types:
registration_begin: User started registrationregistration_complete: User completed registration (success/failure)login_begin: User started loginlogin_complete: User completed login (success/failure)user_access: User accessedGET /users/{id}(success/failure)
| Variable | Default | Description |
|---|---|---|
JWT_SECRET |
your-secret-key-change-in-production |
|
JWT_ALGORITHM |
HS256 |
Algorithm for JWT signing |
JWT_EXPIRATION_MINUTES |
60 |
Token lifetime in minutes |
RP_ID |
localhost |
WebAuthn Relying Party ID (your domain) |
RP_NAME |
Auth Example |
WebAuthn Relying Party display name |
ORIGIN |
http://localhost:8000 |
Expected origin for WebAuthn (must be HTTPS in production) |
- Set
JWT_SECRETto cryptographically random value (32+ bytes) - Store
JWT_SECRETin secret manager (Vault, AWS Secrets Manager) - Set
RP_IDto your production domain (e.g.,auth.example.com) - Set
ORIGINto HTTPS URL (e.g.,https://auth.example.com) - Configure TLS termination at reverse proxy
- Set up centralized logging (ELK, Splunk, CloudWatch)
- Implement rate limiting (Redis +
slowapior WAF) - Configure CORS with specific allowed origins
- Set up database backups
- Enable database encryption at rest
- Configure WAF and DDoS protection
- Set up monitoring and alerting
To test registration multiple times (useful when you only have one device with TouchID):
# Option 1: Python script (clears database and logs)
python3 reset_db.py
# Option 2: Shell script (clears database, restarts server)
./reset.shThis deletes app.db and app.log, allowing you to re-register with the same email.
View all events for a user:
sqlite3 app.db "SELECT created_at, event_type, ip_address, success, details
FROM audit_logs
WHERE user_email='jane@example.com'
ORDER BY created_at DESC;"Find failed login attempts:
sqlite3 app.db "SELECT created_at, user_email, ip_address, user_agent, details
FROM audit_logs
WHERE event_type='login_complete' AND success=0
ORDER BY created_at DESC;"Detect suspicious activity (multiple IPs):
sqlite3 app.db "SELECT user_email, ip_address, COUNT(*) as attempts
FROM audit_logs
WHERE event_type='login_complete' AND success=1
GROUP BY user_email, ip_address;"View authorization failures:
sqlite3 app.db "SELECT created_at, user_email, ip_address, details
FROM audit_logs
WHERE event_type='user_access' AND success=0;"sqlite3 app.db "UPDATE users SET role='admin' WHERE email='admin@example.com';"After promoting, the user must log out and log in again to receive a new JWT with the admin role claim.
- Register two users:
user1@example.comanduser2@example.com - Login as
user1@example.com→ Save JWT token - Try to access
user2's data withuser1's token:Expected:curl -H "Authorization: Bearer <user1-token>" \ http://localhost:8000/users/<user2-id>
403 Forbidden+ audit log entry - Promote
user1to admin:UPDATE users SET role='admin' WHERE email='user1@example.com'; - Login as
user1@example.comagain (to get new JWT withadminrole) - Try to access
user2's data withuser1's admin token:Expected:curl -H "Authorization: Bearer <user1-admin-token>" \ http://localhost:8000/users/<user2-id>
200 OK+user2's data + audit log entry with(role: admin)
This implementation is production-ready for WebAuthn-only authentication. For future enhancements and multi-provider authentication support (SAML, OAuth2/OIDC), see:
MULTI_AUTH_ARCHITECTURE.md - Detailed roadmap for extending this microservice to support:
- SAML 2.0 (Enterprise SSO with Okta, Azure AD, OneLogin)
- OAuth2/OIDC (Social login with Google, GitHub, Microsoft)
- Account linking (users can authenticate with multiple providers)
- Provider abstraction layer for extensibility
The multi-provider architecture maintains full backward compatibility with the current WebAuthn implementation while adding enterprise SSO and social login capabilities.
This is an example project for educational purposes.