# Authentication Flow Fix Design ## Problem Statement The current implementation conflates domain verification (one-time DNS check) with user authentication (per-login email verification). This creates a security vulnerability where only the first user needs to authenticate via email code, while subsequent users bypass authentication entirely. ## Core Concepts ### Domain Verification - **Purpose**: Establish that a domain is configured to use this IndieAuth server - **Method**: DNS TXT record containing server-specific verification string - **Frequency**: Once per domain, results cached in database - **Storage**: `domains` table with verification status and timestamp ### User Authentication - **Purpose**: Prove the current user owns the claimed identity - **Method**: Time-limited 6-digit code sent to rel="me" email - **Frequency**: EVERY authorization attempt - **Storage**: Temporary session storage, expires after 5-10 minutes ## Corrected Authorization Flow ### Step 1: Authorization Request Client initiates OAuth flow: ``` GET /authorize? response_type=code& client_id=https://app.example.com& redirect_uri=https://app.example.com/callback& state=xyz& code_challenge=abc& code_challenge_method=S256& me=https://user.example.com ``` ### Step 2: Domain Verification Check 1. Extract domain from `me` parameter 2. Check `domains` table for existing verification: ```sql SELECT verified, last_checked FROM domains WHERE domain = 'user.example.com' ``` 3. If not verified or stale (>24 hours): - Check DNS TXT record at `_indieauth.user.example.com` - Update database with verification status 4. If domain not verified, reject with error ### Step 3: Profile Discovery 1. Fetch the user's homepage at `me` URL 2. Parse for IndieAuth metadata: - Authorization endpoint (must be this server) - Token endpoint (if present) - rel="me" links for authentication options 3. Extract email from rel="me" links ### Step 4: User Authentication (ALWAYS REQUIRED) 1. Generate 6-digit code 2. Store in session with expiration: ```json { "session_id": "uuid", "me": "https://user.example.com", "email": "user@example.com", "code": "123456", "client_id": "https://app.example.com", "redirect_uri": "https://app.example.com/callback", "state": "xyz", "code_challenge": "abc", "expires_at": "2024-01-01T12:05:00Z" } ``` 3. Send code via email 4. Show code entry form ### Step 5: Code Verification 1. User submits code 2. Validate against session storage 3. If valid, mark session as authenticated 4. If invalid, allow retry (max 3 attempts) ### Step 6: Consent 1. Show consent page with client details 2. User approves/denies 3. If approved, generate authorization code ### Step 7: Authorization Code 1. Generate authorization code 2. Store with session binding: ```json { "code": "auth_code_xyz", "session_id": "uuid", "me": "https://user.example.com", "client_id": "https://app.example.com", "redirect_uri": "https://app.example.com/callback", "code_challenge": "abc", "expires_at": "2024-01-01T12:10:00Z" } ``` 3. Redirect to client with code ## Data Models ### domains table (persistent) ```sql CREATE TABLE domains ( domain VARCHAR(255) PRIMARY KEY, verified BOOLEAN DEFAULT FALSE, verification_string VARCHAR(255), last_checked TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); ``` ### auth_sessions table (temporary, cleaned periodically) ```sql CREATE TABLE auth_sessions ( session_id VARCHAR(255) PRIMARY KEY, me VARCHAR(255) NOT NULL, email VARCHAR(255), verification_code VARCHAR(6), code_verified BOOLEAN DEFAULT FALSE, client_id VARCHAR(255) NOT NULL, redirect_uri VARCHAR(255) NOT NULL, state VARCHAR(255), code_challenge VARCHAR(255), code_challenge_method VARCHAR(10), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL, INDEX idx_expires (expires_at) ); ``` ### authorization_codes table (temporary) ```sql CREATE TABLE authorization_codes ( code VARCHAR(255) PRIMARY KEY, session_id VARCHAR(255) NOT NULL, me VARCHAR(255) NOT NULL, client_id VARCHAR(255) NOT NULL, redirect_uri VARCHAR(255) NOT NULL, code_challenge VARCHAR(255), used BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL, FOREIGN KEY (session_id) REFERENCES auth_sessions(session_id), INDEX idx_expires (expires_at) ); ``` ## Session Management ### Session Creation - Generate UUID for session_id - Set expiration to 10 minutes for email verification - Store all OAuth parameters in session ### Session Validation - Check expiration on every access - Verify session_id matches throughout flow - Clear expired sessions periodically (cron job) ### Security Considerations - Session IDs must be cryptographically random - Email codes must be 6 random digits - Authorization codes must be unguessable - All temporary data expires and is cleaned up ## Error Handling ### Domain Not Verified ```json { "error": "unauthorized_client", "error_description": "Domain not configured for this IndieAuth server" } ``` ### Invalid Email Code ```json { "error": "access_denied", "error_description": "Invalid verification code" } ``` ### Session Expired ```json { "error": "invalid_request", "error_description": "Session expired, please start over" } ``` ## Migration from Current Implementation 1. **Immediate**: Disable caching of email verification 2. **Add auth_sessions table**: Track per-login authentication state 3. **Modify verification flow**: Always require email code 4. **Update domain verification**: Separate from user authentication 5. **Clean up old code**: Remove improper caching logic ## Testing Requirements ### Unit Tests - Domain verification logic (DNS lookup, caching) - Session management (creation, expiration, cleanup) - Email code generation and validation - Authorization code generation and exchange ### Integration Tests - Full authorization flow with email verification - Multiple concurrent users for same domain - Session expiration during flow - Domain verification caching behavior ### Security Tests - Ensure email verification required every login - Verify sessions properly isolated between users - Test rate limiting on code attempts - Verify all codes are single-use ## Acceptance Criteria 1. ✓ Domain verification via DNS TXT is cached appropriately 2. ✓ Email verification code is required for EVERY login attempt 3. ✓ Multiple users can authenticate for the same domain independently 4. ✓ Sessions expire and are cleaned up properly 5. ✓ Authorization codes are single-use 6. ✓ Clear separation between domain verification and user authentication 7. ✓ No security regression from current (broken) implementation ## Implementation Priority **CRITICAL**: This is a security vulnerability that must be fixed immediately. The current implementation allows unauthenticated access after the first user logs in for a domain. ## Notes The confusion between domain verification and user authentication is a fundamental architectural error. This fix properly separates these concerns: - **Domain verification** establishes trust in the domain configuration (one-time) - **User authentication** establishes trust in the current user (every time) This aligns with the IndieAuth specification where the authorization endpoint MUST authenticate the user, not just verify the domain.