# Authentication Architecture This document describes the comprehensive authentication and authorization strategy implemented in rick-infra, focusing on centralized SSO with Authentik and forward authentication integration. ## Overview Rick-infra implements a modern, security-focused authentication architecture that provides: - **Centralized SSO**: Single sign-on across all services via Authentik - **Forward Authentication**: Transparent protection without application changes - **Zero Network Exposure**: Database and cache communication via Unix sockets - **Granular Authorization**: Fine-grained access control through groups and policies - **Standards Compliance**: OAuth2, OIDC, SAML support for enterprise integration ## Architecture Components ``` ┌─────────────────────────────────────────────────────────────────┐ │ rick-infra Authentication Architecture │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ Users │ │ Caddy │ │ Authentik │ │ │ │ │───▶│ (Proxy) │───▶│ (Auth Server) │ │ │ │ Web Browser │ │ Forward Auth│ │ OAuth2/OIDC/SAML │ │ │ │ Mobile Apps │ │ TLS Term │ │ User Management │ │ │ │ API Clients │ │ Load Balance│ │ Policy Engine │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ Protected Services │ │ │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ │ │ Gitea │ │ Gallery │ │ Custom Services │ │ │ │ │ │ (Git Repos) │ │ (Media) │ │ (Applications) │ │ │ │ │ │ Receives: │ │ Receives: │ │ Receives: │ │ │ │ │ │ Remote-User │ │ Remote-User │ │ Remote-User │ │ │ │ │ │ Remote-Email│ │ Remote-Name │ │ Remote-Groups │ │ │ │ │ └─────────────┘ └─────────────┘ └─────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ Infrastructure (Unix Sockets) │ │ │ │ PostgreSQL Database • Valkey Cache • systemd Services │ │ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` ## Authentication Flow ### Standard User Authentication Flow ```mermaid sequenceDiagram participant U as User participant C as Caddy participant A as Authentik participant S as Protected Service U->>C: GET /dashboard C->>A: Forward Auth Request A->>C: 401 Unauthorized C->>U: 302 Redirect to Login U->>A: Login Form Request A->>U: Login Form U->>A: Credentials A->>U: Set Session Cookie U->>C: GET /dashboard (with cookie) C->>A: Forward Auth Request (with cookie) A->>C: 200 OK + User Headers C->>S: GET /dashboard + Headers S->>C: Dashboard Content C->>U: Dashboard Content ``` ### API Authentication Flow ```mermaid sequenceDiagram participant API as API Client participant A as Authentik participant C as Caddy participant S as API Service API->>A: POST /application/o/token/ Note over API,A: OAuth2 Client Credentials A->>API: Access Token API->>C: GET /api/data Note over API,C: Authorization: Bearer {token} C->>A: Token Validation A->>C: Token Valid + Claims C->>S: GET /api/data + Headers S->>API: API Response ``` ## Service Integration Patterns ### Pattern 1: Forward Authentication (Recommended) **Use Case**: Existing HTTP services that don't need to handle authentication **Benefits**: - No application code changes required - Consistent authentication across all services - Service receives authenticated user information via headers - Centralized session management **Implementation**: ```caddyfile # Caddy configuration myservice.jnss.me { forward_auth https://auth.jnss.me { uri /outpost.goauthentik.io/auth/caddy copy_headers Remote-User Remote-Name Remote-Email Remote-Groups } reverse_proxy localhost:8080 } ``` **Service Code Example** (Python Flask): ```python from flask import Flask, request app = Flask(__name__) @app.route('/dashboard') def dashboard(): # Authentication handled by Caddy/Authentik username = request.headers.get('Remote-User') user_name = request.headers.get('Remote-Name') user_email = request.headers.get('Remote-Email') user_groups = request.headers.get('Remote-Groups', '').split(',') # Authorization based on groups if 'admins' not in user_groups: return "Access denied", 403 return f"Welcome {user_name} ({username})" ``` ### Pattern 2: OAuth2/OIDC Integration **Use Case**: Applications that can implement OAuth2 client functionality **Benefits**: - More control over authentication flow - Better integration with application user models - Support for API access tokens - Offline access via refresh tokens **Implementation**: ```python # Example OAuth2 configuration OAUTH2_CONFIG = { 'client_id': 'your-client-id', 'client_secret': 'your-client-secret', 'server_metadata_url': 'https://auth.jnss.me/application/o/your-app/.well-known/openid_configuration', 'client_kwargs': { 'scope': 'openid email profile groups' } } # OAuth2 flow implementation from authlib.integrations.flask_client import OAuth oauth = OAuth(app) oauth.register('authentik', **OAUTH2_CONFIG) @app.route('/login') def login(): redirect_uri = url_for('callback', _external=True) return oauth.authentik.authorize_redirect(redirect_uri) @app.route('/callback') def callback(): token = oauth.authentik.authorize_access_token() user_info = oauth.authentik.parse_id_token(token) # Store user info in session session['user'] = user_info return redirect('/dashboard') ``` ### Pattern 3: API-Only Authentication **Use Case**: REST APIs, mobile app backends, microservices **Benefits**: - Stateless authentication via tokens - Machine-to-machine authentication support - Fine-grained API scope control - Easy integration with mobile/SPA applications **Implementation**: ```yaml # Authentik Provider Configuration name: "API Provider" authorization_flow: "default-provider-authorization-explicit-consent" client_type: "confidential" client_id: "api-client-id" redirect_uris: ["http://localhost:8080/callback"] ``` ```python # API service with token validation import requests from flask import Flask, request, jsonify def validate_token(token): """Validate token with Authentik introspection endpoint""" response = requests.post( 'https://auth.jnss.me/application/o/introspect/', headers={'Authorization': f'Bearer {token}'}, data={'token': token} ) return response.json() if response.status_code == 200 else None @app.route('/api/data') def api_data(): auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return jsonify({'error': 'Missing or invalid authorization header'}), 401 token = auth_header.split(' ')[1] token_info = validate_token(token) if not token_info or not token_info.get('active'): return jsonify({'error': 'Invalid or expired token'}), 401 # Extract user information from token username = token_info.get('username') scope = token_info.get('scope', '').split() if 'read:data' not in scope: return jsonify({'error': 'Insufficient permissions'}), 403 return jsonify({'message': f'Hello {username}', 'data': 'sensitive-data'}) ``` ### Pattern 4: Service-to-Service Authentication **Use Case**: Backend services communicating with each other **Benefits**: - Machine-to-machine authentication - Service identity and authorization - API rate limiting and monitoring - Secure inter-service communication **Implementation**: ```yaml # Authentik Service Account Configuration name: "Backend Service Account" username: "backend-service" groups: ["services", "backend-access"] token_validity: "8760h" # 1 year ``` ```python # Service-to-service authentication import os import requests class ServiceClient: def __init__(self): self.client_id = os.getenv('SERVICE_CLIENT_ID') self.client_secret = os.getenv('SERVICE_CLIENT_SECRET') self.token_url = 'https://auth.jnss.me/application/o/token/' self.access_token = None def get_access_token(self): if not self.access_token: response = requests.post(self.token_url, data={ 'grant_type': 'client_credentials', 'client_id': self.client_id, 'client_secret': self.client_secret, 'scope': 'service:read service:write' }) self.access_token = response.json()['access_token'] return self.access_token def call_service(self, endpoint): token = self.get_access_token() response = requests.get( f'https://api.jnss.me{endpoint}', headers={'Authorization': f'Bearer {token}'} ) return response.json() ``` ## User Management Strategy ### User Lifecycle Management #### User Creation and Onboarding ```yaml # Authentik User Configuration email: "user@company.com" username: "user.name" name: "User Full Name" is_active: true groups: ["employees", "developers"] # Assigned based on role attributes: department: "Engineering" team: "Backend" hire_date: "2024-01-15" ``` #### Group-Based Authorization ```yaml # Group Structure groups: - name: "employees" description: "All company employees" permissions: ["basic_access"] - name: "developers" description: "Software development team" permissions: ["git_access", "deploy_staging"] parent_group: "employees" - name: "admins" description: "System administrators" permissions: ["admin_access", "deploy_production"] parent_group: "employees" - name: "contractors" description: "External contractors" permissions: ["limited_access"] ``` #### Access Control Policies ```yaml # Authentik Policy Configuration policies: - name: "Business Hours Access" type: "time" parameters: start_time: "08:00" end_time: "18:00" days: ["monday", "tuesday", "wednesday", "thursday", "friday"] - name: "Admin IP Restriction" type: "source_ip" parameters: cidr: "10.0.0.0/8" - name: "MFA Required for Admin" type: "group_membership" parameters: group: "admins" require_mfa: true ``` ### Multi-Factor Authentication #### MFA Configuration ```yaml # Authentik MFA Settings mfa_policies: - name: "Admin MFA Requirement" bound_to: "group:admins" authenticators: ["totp", "webauthn"] enforce: true - name: "Developer Optional MFA" bound_to: "group:developers" authenticators: ["totp", "sms"] enforce: false ``` #### Supported MFA Methods 1. **TOTP (Time-based One-Time Passwords)** - Google Authenticator, Authy, 1Password - RFC 6238 compliant - Configurable validity period 2. **WebAuthn/FIDO2** - Hardware security keys (YubiKey, etc.) - Biometric authentication - Passwordless authentication support 3. **SMS Authentication** - SMS-based verification codes - International phone number support - Rate limiting and abuse protection 4. **Email Authentication** - Email-based verification codes - Fallback authentication method - Configurable code validity ## Security Considerations ### Session Management #### Session Configuration ```yaml # Authentik Session Settings session_settings: cookie_age: 3600 # 1 hour cookie_secure: true # HTTPS only cookie_samesite: "Strict" remember_me_duration: 86400 # 24 hours concurrent_sessions: 3 # Max simultaneous sessions ``` #### Session Security Features - **Session Fixation Protection**: New session ID on authentication - **Concurrent Session Control**: Limit simultaneous sessions per user - **Session Timeout**: Automatic logout after inactivity - **Secure Cookies**: HTTP-only, secure, SameSite attributes - **Session Invalidation**: Logout invalidates all sessions ### Authorization Security #### Policy-Based Access Control ```yaml # Example Authorization Policy policy_bindings: - application: "admin_dashboard" policies: - "group_membership:admins" - "mfa_required" - "business_hours_access" - "ip_whitelist:office" order: 0 - application: "developer_tools" policies: - "group_membership:developers" - "rate_limiting:api_calls" order: 1 ``` #### Security Headers ```http # Headers passed to protected services Remote-User: john.doe Remote-Name: John Doe Remote-Email: john.doe@company.com Remote-Groups: employees,developers Remote-Authenticated: true Remote-Auth-Time: 2025-12-11T17:52:31Z Remote-Session-ID: sess_abc123def456 ``` ### Audit and Logging #### Authentication Events ```json { "timestamp": "2025-12-11T17:52:31Z", "event_type": "authentication_success", "user_id": "user123", "username": "john.doe", "source_ip": "192.168.1.100", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "application": "dashboard.jnss.me", "auth_method": "password+totp", "session_id": "sess_abc123def456" } ``` #### Authorization Events ```json { "timestamp": "2025-12-11T17:52:45Z", "event_type": "authorization_denied", "user_id": "user456", "username": "contractor.user", "source_ip": "203.0.113.100", "application": "admin_dashboard", "reason": "insufficient_privileges", "required_groups": ["admins"], "user_groups": ["contractors"] } ``` #### System Events ```json { "timestamp": "2025-12-11T17:53:00Z", "event_type": "policy_violation", "user_id": "user789", "username": "admin.user", "source_ip": "198.51.100.50", "policy": "ip_whitelist:office", "violation": "access_from_unauthorized_ip", "action": "access_denied" } ``` ## Integration Examples ### Example 1: Protecting Gitea with Groups **Objective**: Protect Git repository access with role-based permissions ```caddyfile # Caddy configuration for Gitea git.jnss.me { forward_auth https://auth.jnss.me { uri /outpost.goauthentik.io/auth/caddy copy_headers Remote-User Remote-Groups } reverse_proxy localhost:3000 } ``` **Gitea Configuration**: ```ini # app.ini - Gitea configuration [auth] REVERSE_PROXY_AUTHENTICATION = true REVERSE_PROXY_AUTO_REGISTRATION = true [auth.reverse_proxy] USER_HEADER = Remote-User EMAIL_HEADER = Remote-Email FULL_NAME_HEADER = Remote-Name ``` **Group Mapping**: ```yaml # Authentik group configuration for Gitea groups: - name: "git_users" description: "Git repository users" applications: ["gitea"] - name: "git_admins" description: "Git administrators" applications: ["gitea"] permissions: ["admin"] ``` ### Example 2: API Service with Scoped Access **Objective**: REST API with OAuth2 authentication and scoped permissions ```caddyfile # Caddy configuration for API api.jnss.me { # No forward auth - API handles OAuth2 directly reverse_proxy localhost:8080 } ``` **API Service Configuration**: ```python from flask import Flask, request, jsonify import requests app = Flask(__name__) def verify_token_scope(token, required_scope): """Verify token has required scope""" response = requests.post( 'https://auth.jnss.me/application/o/introspect/', data={ 'token': token, 'client_id': 'api-client-id', 'client_secret': 'api-client-secret' } ) if response.status_code != 200: return False token_data = response.json() if not token_data.get('active'): return False scopes = token_data.get('scope', '').split() return required_scope in scopes @app.route('/api/public') def public_endpoint(): """Public endpoint - no authentication required""" return jsonify({'message': 'This is public data'}) @app.route('/api/user/profile') def user_profile(): """User profile endpoint - requires user:read scope""" auth_header = request.headers.get('Authorization', '') if not auth_header.startswith('Bearer '): return jsonify({'error': 'Missing authorization header'}), 401 token = auth_header[7:] # Remove 'Bearer ' prefix if not verify_token_scope(token, 'user:read'): return jsonify({'error': 'Insufficient permissions'}), 403 return jsonify({'message': 'User profile data'}) @app.route('/api/admin/users') def admin_users(): """Admin endpoint - requires admin:read scope""" auth_header = request.headers.get('Authorization', '') if not auth_header.startswith('Bearer '): return jsonify({'error': 'Missing authorization header'}), 401 token = auth_header[7:] if not verify_token_scope(token, 'admin:read'): return jsonify({'error': 'Admin privileges required'}), 403 return jsonify({'message': 'Admin user data'}) ``` **OAuth2 Scopes Configuration**: ```yaml # Authentik OAuth2 Provider scopes scopes: - name: "user:read" description: "Read user profile information" - name: "user:write" description: "Modify user profile information" - name: "admin:read" description: "Read administrative data" - name: "admin:write" description: "Modify administrative settings" ``` ### Example 3: Static Site with Selective Protection **Objective**: Protect portions of a static site while keeping some content public ```caddyfile # Caddy configuration for mixed static/protected site docs.jnss.me { # Public documentation - no authentication handle /public/* { root * /var/www/docs file_server } # Public API documentation handle /api-docs { root * /var/www/docs file_server } # Protected internal documentation handle /internal/* { forward_auth https://auth.jnss.me { uri /outpost.goauthentik.io/auth/caddy copy_headers Remote-Groups } # Only employees can access internal docs @not_employee { not header Remote-Groups "*employees*" } respond @not_employee "Access denied" 403 root * /var/www/docs file_server } # Admin documentation - requires admin group handle /admin/* { forward_auth https://auth.jnss.me { uri /outpost.goauthentik.io/auth/caddy copy_headers Remote-Groups } # Only admins can access admin docs @not_admin { not header Remote-Groups "*admins*" } respond @not_admin "Admin access required" 403 root * /var/www/docs file_server } # Default: Require authentication for everything else handle { forward_auth https://auth.jnss.me { uri /outpost.goauthentik.io/auth/caddy copy_headers Remote-User Remote-Groups } root * /var/www/docs file_server } } ``` ## Performance Considerations ### Authentication Performance #### Caching Strategy - **Session Caching**: Redis-backed session storage for fast lookup - **Token Caching**: Cache valid tokens to reduce introspection calls - **User Information Caching**: Cache user attributes and group memberships - **Policy Evaluation Caching**: Cache authorization decision results #### Performance Optimization ```yaml # Authentik Performance Settings cache_settings: default_timeout: 300 # 5 minutes session_timeout: 1800 # 30 minutes token_introspection_cache: 60 # 1 minute user_info_cache: 600 # 10 minutes ``` #### Load Balancing Considerations - **Session Affinity**: Not required - sessions stored in shared cache - **Health Checks**: Monitor authentik container health - **Failover**: Graceful degradation strategies for auth service outages - **Rate Limiting**: Protect against authentication abuse ### Database Performance #### Connection Optimization ```yaml # PostgreSQL connection settings for Authentik database_settings: max_connections: 100 connection_timeout: 30 idle_timeout: 300 query_timeout: 60 connection_pool_size: 20 ``` #### Index Optimization ```sql -- Recommended PostgreSQL indexes for Authentik performance CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_authentik_core_user_email ON authentik_core_user(email); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_authentik_core_token_key ON authentik_core_token(key); CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_authentik_sessions_expire_date ON django_session(expire_date); ``` ## Troubleshooting Guide ### Common Authentication Issues #### Issue: Users Cannot Access Protected Services **Symptoms**: - 401 Unauthorized errors - Redirect loops to login page - "Access denied" messages **Diagnostic Steps**: ```bash # Check authentik service status ssh root@your-vps "systemctl --user -M authentik@ status authentik-server" # Test authentik HTTP endpoint curl -I https://auth.jnss.me/ # Check forward auth endpoint curl -I https://auth.jnss.me/outpost.goauthentik.io/auth/caddy # Verify Caddy configuration ssh root@your-vps "caddy validate --config /etc/caddy/Caddyfile" ``` #### Issue: Group-Based Authorization Not Working **Symptoms**: - Users with correct groups denied access - Group headers not passed to services - Authorization policies not applied **Diagnostic Steps**: ```bash # Check user group membership curl -H "Authorization: Bearer $TOKEN" https://auth.jnss.me/api/v3/core/users/me/ # Test header passing curl -H "Cookie: authentik_session=$SESSION" https://auth.jnss.me/outpost.goauthentik.io/auth/caddy -v # Verify Caddy header configuration ssh root@your-vps "grep -A5 'copy_headers' /etc/caddy/sites-enabled/*.caddy" ``` #### Issue: OAuth2 Token Validation Failing **Symptoms**: - "Invalid token" errors for valid tokens - Token introspection returning false - API calls failing with 401 **Diagnostic Steps**: ```bash # Test token introspection directly curl -X POST https://auth.jnss.me/application/o/introspect/ \ -H "Authorization: Bearer $CLIENT_TOKEN" \ -d "token=$USER_TOKEN" # Check client credentials curl -X POST https://auth.jnss.me/application/o/token/ \ -d "grant_type=client_credentials" \ -d "client_id=$CLIENT_ID" \ -d "client_secret=$CLIENT_SECRET" ``` ### Performance Issues #### Issue: Slow Authentication Response **Symptoms**: - Long delays on login - Timeouts during authentication - Poor user experience **Diagnostic Steps**: ```bash # Check authentik container resources ssh root@your-vps "sudo -u authentik podman stats authentik-server" # Monitor database performance ssh root@your-vps "sudo -u postgres psql -h /var/run/postgresql -c 'SELECT * FROM pg_stat_activity;'" # Check network latency curl -w "@curl-format.txt" -o /dev/null https://auth.jnss.me/ ``` #### Issue: High Database Load **Symptoms**: - Slow database queries - High CPU usage on database - Authentication timeouts **Solutions**: ```sql -- Optimize authentication queries VACUUM ANALYZE authentik_core_user; VACUUM ANALYZE authentik_core_token; VACUUM ANALYZE django_session; -- Check for missing indexes SELECT schemaname, tablename, attname, n_distinct, correlation FROM pg_stats WHERE schemaname = 'public' AND tablename LIKE 'authentik_%'; ``` ## Future Considerations ### Scalability Planning #### Horizontal Scaling - **Load Balancer**: Multiple authentik instances behind load balancer - **Database Clustering**: PostgreSQL replication for read scaling - **Cache Clustering**: Valkey cluster for session storage - **Geographic Distribution**: Regional authentik deployments #### Performance Monitoring ```yaml # Monitoring metrics to track metrics: authentication: - login_success_rate - login_response_time - failed_login_attempts - concurrent_sessions authorization: - authorization_success_rate - policy_evaluation_time - access_denied_rate system: - database_connection_pool_usage - cache_hit_rate - container_memory_usage - response_time_p95 ``` ### Enterprise Integration #### SAML Federation ```yaml # SAML Provider configuration for enterprise SSO saml_provider: name: "Corporate SAML" acs_url: "https://auth.jnss.me/source/saml/corporate/acs/" issuer: "https://auth.jnss.me" signing_certificate: "{{ saml_signing_cert }}" encryption_certificate: "{{ saml_encryption_cert }}" ``` #### LDAP/Active Directory Integration ```yaml # LDAP source configuration ldap_source: name: "Corporate LDAP" server_uri: "ldaps://ldap.company.com:636" bind_dn: "CN=authentik,OU=Service Accounts,DC=company,DC=com" bind_password: "{{ ldap_bind_password }}" base_dn: "DC=company,DC=com" user_search: "(&(objectClass=user)(sAMAccountName=%(user)s))" group_search: "(&(objectClass=group)(member=%(user_dn)s))" ``` --- This comprehensive authentication architecture provides a robust, scalable, and secure foundation for managing access to all services in the rick-infra environment, with emphasis on security, performance, and operational excellence. ## References - **[Authentik Deployment Guide](authentik-deployment-guide.md)** - Detailed deployment instructions - **[Architecture Decisions](architecture-decisions.md)** - Technical decision rationale - **[Service Integration Guide](service-integration-guide.md)** - Adding new services - **[Security Hardening](security-hardening.md)** - Security implementation details - **[Authentik Role Documentation](../roles/authentik/README.md)** - Technical implementation details