# IndieAuth Endpoint Discovery Architecture ## Overview This document details the CORRECT implementation of IndieAuth endpoint discovery for StarPunk. This corrects a fundamental misunderstanding where endpoints were incorrectly hardcoded instead of being discovered dynamically. ## Core Principle **Endpoints are NEVER hardcoded. They are ALWAYS discovered from the user's profile URL.** ## Discovery Process ### Step 1: Profile URL Fetching When discovering endpoints for a user (e.g., `https://alice.example.com/`): ``` GET https://alice.example.com/ HTTP/1.1 Accept: text/html User-Agent: StarPunk/1.0 ``` ### Step 2: Endpoint Extraction Check in priority order: #### 2.1 HTTP Link Headers (Highest Priority) ``` Link: ; rel="authorization_endpoint", ; rel="token_endpoint" ``` #### 2.2 HTML Link Elements ```html ``` #### 2.3 IndieAuth Metadata (Optional) ```html ``` ### Step 3: URL Resolution All discovered URLs must be resolved relative to the profile URL: - Absolute URL: Use as-is - Relative URL: Resolve against profile URL - Protocol-relative: Inherit profile URL protocol ## Token Verification Architecture ### The Problem When Micropub receives a token, it needs to verify it. But with which endpoint? ### The Solution ``` ┌─────────────────┐ │ Micropub Request│ │ Bearer: xxxxx │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Extract Token │ └────────┬────────┘ │ ▼ ┌─────────────────────────┐ │ Determine User Identity │ │ (from token or cache) │ └────────┬────────────────┘ │ ▼ ┌──────────────────────┐ │ Discover Endpoints │ │ from User Profile │ └────────┬─────────────┘ │ ▼ ┌──────────────────────┐ │ Verify with │ │ Discovered Endpoint │ └────────┬─────────────┘ │ ▼ ┌──────────────────────┐ │ Validate Response │ │ - Check 'me' URL │ │ - Check scopes │ └──────────────────────┘ ``` ## Implementation Components ### 1. Endpoint Discovery Module ```python class EndpointDiscovery: """ Discovers IndieAuth endpoints from profile URLs """ def discover(self, profile_url: str) -> Dict[str, str]: """ Discover endpoints from a profile URL Returns: { 'authorization_endpoint': 'https://...', 'token_endpoint': 'https://...', 'indieauth_metadata': 'https://...' # optional } """ def parse_link_header(self, header: str) -> Dict[str, str]: """Parse HTTP Link header for endpoints""" def extract_from_html(self, html: str, base_url: str) -> Dict[str, str]: """Extract endpoints from HTML link elements""" def resolve_url(self, url: str, base: str) -> str: """Resolve potentially relative URL against base""" ``` ### 2. Token Verification Module ```python class TokenVerifier: """ Verifies tokens using discovered endpoints """ def __init__(self, discovery: EndpointDiscovery, cache: EndpointCache): self.discovery = discovery self.cache = cache def verify(self, token: str, expected_me: str = None) -> TokenInfo: """ Verify a token using endpoint discovery Args: token: The bearer token to verify expected_me: Optional expected 'me' URL Returns: TokenInfo with 'me', 'scope', 'client_id', etc. """ def introspect_token(self, token: str, endpoint: str) -> dict: """Call token endpoint to verify token""" ``` ### 3. Caching Layer ```python class EndpointCache: """ Caches discovered endpoints for performance """ def __init__(self, ttl: int = 3600): self.endpoint_cache = {} # profile_url -> (endpoints, expiry) self.token_cache = {} # token_hash -> (info, expiry) self.ttl = ttl def get_endpoints(self, profile_url: str) -> Optional[Dict[str, str]]: """Get cached endpoints if still valid""" def store_endpoints(self, profile_url: str, endpoints: Dict[str, str]): """Cache discovered endpoints""" def get_token_info(self, token_hash: str) -> Optional[TokenInfo]: """Get cached token verification if still valid""" def store_token_info(self, token_hash: str, info: TokenInfo): """Cache token verification result""" ``` ## Error Handling ### Discovery Failures | Error | Cause | Response | |-------|-------|----------| | ProfileUnreachableError | Can't fetch profile URL | 503 Service Unavailable | | NoEndpointsFoundError | No endpoints in profile | 400 Bad Request | | InvalidEndpointError | Malformed endpoint URL | 500 Internal Server Error | | TimeoutError | Discovery timeout | 504 Gateway Timeout | ### Verification Failures | Error | Cause | Response | |-------|-------|----------| | TokenInvalidError | Token rejected by endpoint | 403 Forbidden | | EndpointUnreachableError | Can't reach token endpoint | 503 Service Unavailable | | ScopeMismatchError | Token lacks required scope | 403 Forbidden | | MeMismatchError | Token 'me' doesn't match expected | 403 Forbidden | ## Security Considerations ### 1. HTTPS Enforcement - Profile URLs SHOULD use HTTPS - Discovered endpoints MUST use HTTPS - Reject non-HTTPS endpoints in production ### 2. Redirect Limits - Maximum 5 redirects when fetching profiles - Prevent redirect loops - Log suspicious redirect patterns ### 3. Cache Poisoning Prevention - Validate discovered URLs are well-formed - Don't cache error responses - Clear cache on configuration changes ### 4. Token Security - Never log tokens in plaintext - Hash tokens before caching - Use constant-time comparison for token hashes ## Performance Optimization ### Caching Strategy ``` ┌─────────────────────────────────────┐ │ First Request │ │ Discovery: ~500ms │ │ Verification: ~200ms │ │ Total: ~700ms │ └─────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────┐ │ Subsequent Requests │ │ Cached Endpoints: ~1ms │ │ Cached Token: ~1ms │ │ Total: ~2ms │ └─────────────────────────────────────┘ ``` ### Cache Configuration ```ini # Endpoint cache (user rarely changes provider) ENDPOINT_CACHE_TTL=3600 # 1 hour # Token cache (balance security and performance) TOKEN_CACHE_TTL=300 # 5 minutes # Cache sizes MAX_ENDPOINT_CACHE_SIZE=1000 MAX_TOKEN_CACHE_SIZE=10000 ``` ## Migration Path ### From Incorrect Hardcoded Implementation 1. Remove hardcoded endpoint configuration 2. Implement discovery module 3. Update token verification to use discovery 4. Add caching layer 5. Update documentation ### Configuration Changes Before (WRONG): ```ini TOKEN_ENDPOINT=https://tokens.indieauth.com/token AUTHORIZATION_ENDPOINT=https://indieauth.com/auth ``` After (CORRECT): ```ini ADMIN_ME=https://admin.example.com/ # Endpoints discovered automatically from ADMIN_ME ``` ## Testing Strategy ### Unit Tests 1. **Discovery Tests** - Parse various Link header formats - Extract from different HTML structures - Handle malformed responses - URL resolution edge cases 2. **Cache Tests** - TTL expiration - Cache invalidation - Size limits - Concurrent access 3. **Security Tests** - HTTPS enforcement - Redirect limit enforcement - Cache poisoning attempts ### Integration Tests 1. **Real Provider Tests** - Test against indieauth.com - Test against indie-auth.com - Test against self-hosted providers 2. **Network Condition Tests** - Slow responses - Timeouts - Connection failures - Partial responses ### End-to-End Tests 1. **Full Flow Tests** - Discovery → Verification → Caching - Multiple users with different providers - Provider switching scenarios ## Monitoring and Debugging ### Metrics to Track - Discovery success/failure rate - Average discovery latency - Cache hit ratio - Token verification latency - Endpoint availability ### Debug Logging ```python # Discovery DEBUG: Fetching profile URL: https://alice.example.com/ DEBUG: Found Link header: ; rel="token_endpoint" DEBUG: Discovered token endpoint: https://auth.alice.net/token # Verification DEBUG: Verifying token for claimed identity: https://alice.example.com/ DEBUG: Using cached endpoint: https://auth.alice.net/token DEBUG: Token verification successful, scopes: ['create', 'update'] # Caching DEBUG: Caching endpoints for https://alice.example.com/ (TTL: 3600s) DEBUG: Token verification cached (TTL: 300s) ``` ## Common Issues and Solutions ### Issue 1: No Endpoints Found **Symptom**: "No token endpoint found for user" **Causes**: - User hasn't set up IndieAuth on their profile - Profile URL returns wrong Content-Type - Link elements have typos **Solution**: - Provide clear error message - Link to IndieAuth setup documentation - Log details for debugging ### Issue 2: Verification Timeouts **Symptom**: "Authorization server is unreachable" **Causes**: - Auth server is down - Network issues - Firewall blocking requests **Solution**: - Implement retries with backoff - Cache successful verifications - Provide status page for auth server health ### Issue 3: Cache Invalidation **Symptom**: User changed provider but old one still used **Causes**: - Endpoints still cached - TTL too long **Solution**: - Provide manual cache clear option - Reduce TTL if needed - Clear cache on errors ## Appendix: Example Discoveries ### Example 1: IndieAuth.com User ```html ``` ### Example 2: Self-Hosted ```html ``` ### Example 3: Link Headers ``` HTTP/1.1 200 OK Link: ; rel="authorization_endpoint", ; rel="token_endpoint" Content-Type: text/html ``` ### Example 4: Relative URLs ```html ``` --- **Document Version**: 1.0 **Created**: 2024-11-24 **Purpose**: Correct implementation of IndieAuth endpoint discovery **Status**: Authoritative guide for implementation