- Update architecture-decisions.md: Change decision to OAuth/OIDC primary, forward auth fallback - Add comprehensive OAuth/OIDC and forward auth flow diagrams - Add decision matrix comparing both authentication methods - Include real examples: Nextcloud/Gitea OAuth configs, whoami forward auth - Update rationale to emphasize OAuth/OIDC security and standards benefits - Update authentication-architecture.md: Align with new OAuth-first approach - Add 'Choosing the Right Pattern' section with clear decision guidance - Swap pattern order: OAuth/OIDC (Pattern 1), Forward Auth (Pattern 2) - Update Example 1: Change Gitea from forward auth to OAuth/OIDC integration - Add emphasis on primary vs fallback methods throughout - Update authentik-deployment-guide.md: Reflect OAuth/OIDC preference - Update overview to mention OAuth2/OIDC provider and forward auth fallback - Add decision guidance to service integration examples - Reorder examples: Nextcloud OAuth (primary), forward auth (fallback) - Clarify forward auth should only be used for services without OAuth support This update ensures all authentication documentation consistently reflects the agreed architectural decision: use OAuth/OIDC when services support it (Nextcloud, Gitea, modern apps), and only use forward auth as a fallback for legacy applications, static sites, or simple tools without OAuth capabilities.
1018 lines
30 KiB
Markdown
1018 lines
30 KiB
Markdown
# 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
|
|
- **OAuth2/OIDC Integration**: Industry-standard authentication for services that support it (primary method)
|
|
- **Forward Authentication**: Transparent protection for legacy applications and services without OAuth support (fallback method)
|
|
- **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
|
|
|
|
### Choosing the Right Pattern
|
|
|
|
**Use OAuth2/OIDC (Primary Method)** when:
|
|
- ✅ Your service/application natively supports OAuth2/OIDC
|
|
- ✅ Examples: Nextcloud, Gitea, Grafana, modern web applications
|
|
- ✅ Provides better security, standard protocols, and proper token management
|
|
|
|
**Use Forward Authentication (Fallback Method)** when:
|
|
- ⚠️ Service doesn't support OAuth2/OIDC integration
|
|
- ⚠️ Legacy applications that cannot be modified
|
|
- ⚠️ Static sites requiring authentication
|
|
- ⚠️ Simple internal tools without authentication capabilities
|
|
|
|
---
|
|
|
|
### Pattern 1: OAuth2/OIDC Integration (Primary Method)
|
|
|
|
**Use Case**: Applications with native OAuth2/OIDC support (Nextcloud, Gitea, Grafana, etc.)
|
|
|
|
**Benefits**:
|
|
- ✅ Industry-standard authentication protocol (RFC 6749, RFC 7636)
|
|
- ✅ Secure token-based authentication with JWT
|
|
- ✅ Proper session management with refresh tokens
|
|
- ✅ Native application integration
|
|
- ✅ Support for API access tokens
|
|
- ✅ Better logout handling and token refresh
|
|
|
|
**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')
|
|
```
|
|
|
|
**Real-World Example**: See Example 1 below for Gitea OAuth/OIDC configuration.
|
|
|
|
---
|
|
|
|
### Pattern 2: Forward Authentication (Fallback Method)
|
|
|
|
**Use Case**: Existing HTTP services that don't support OAuth2/OIDC
|
|
|
|
**Benefits**:
|
|
- ✅ No application code changes required
|
|
- ✅ Consistent authentication across all services
|
|
- ✅ Service receives authenticated user information via headers
|
|
- ✅ Centralized session management
|
|
- ✅ Works with any HTTP application
|
|
|
|
**Limitations**:
|
|
- ⚠️ Non-standard authentication approach
|
|
- ⚠️ All requests must flow through authenticating proxy
|
|
- ⚠️ Limited logout functionality
|
|
- ⚠️ Backend must trust proxy-provided headers
|
|
|
|
**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})"
|
|
```
|
|
|
|
**Real-World Example**: See Example 3 below for static site protection with forward auth.
|
|
|
|
---
|
|
|
|
### 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: Gitea with OAuth2/OIDC Integration
|
|
|
|
**Objective**: Git repository access with native OAuth2/OIDC authentication
|
|
|
|
**Why OAuth for Gitea**: Gitea has native OAuth2/OIDC support, providing better security and user experience than forward auth.
|
|
|
|
#### Step 1: Configure Authentik OAuth2 Provider
|
|
|
|
```yaml
|
|
# Authentik Provider Configuration
|
|
name: "Gitea OAuth Provider"
|
|
type: "OAuth2/OpenID Provider"
|
|
authorization_flow: "default-provider-authorization-implicit-consent"
|
|
client_type: "confidential"
|
|
client_id: "gitea"
|
|
redirect_uris: "https://git.jnss.me/user/oauth2/Authentik/callback"
|
|
signing_key: "authentik-default-key"
|
|
scopes: ["openid", "profile", "email", "groups"]
|
|
property_mappings:
|
|
- "authentik default OAuth Mapping: OpenID 'openid'"
|
|
- "authentik default OAuth Mapping: OpenID 'email'"
|
|
- "authentik default OAuth Mapping: OpenID 'profile'"
|
|
```
|
|
|
|
#### Step 2: Caddy Configuration
|
|
|
|
```caddyfile
|
|
# Caddy configuration for Gitea - No forward auth needed
|
|
git.jnss.me {
|
|
reverse_proxy localhost:3000
|
|
}
|
|
```
|
|
|
|
#### Step 3: Gitea Configuration
|
|
|
|
```ini
|
|
# app.ini - Gitea OAuth2 configuration
|
|
[openid]
|
|
ENABLE_OPENID_SIGNIN = false
|
|
ENABLE_OPENID_SIGNUP = false
|
|
|
|
[oauth2_client]
|
|
REGISTER_EMAIL_CONFIRM = false
|
|
OPENID_CONNECT_SCOPES = openid email profile groups
|
|
ENABLE_AUTO_REGISTRATION = true
|
|
USERNAME = preferred_username
|
|
EMAIL = email
|
|
ACCOUNT_LINKING = auto
|
|
```
|
|
|
|
#### Step 4: Add OAuth Source in Gitea Web UI
|
|
|
|
1. Navigate to **Site Administration → Authentication Sources**
|
|
2. Click **Add Authentication Source**
|
|
3. **Authentication Type**: OAuth2
|
|
4. **Authentication Name**: Authentik
|
|
5. **OAuth2 Provider**: OpenID Connect
|
|
6. **Client ID**: `gitea` (from Authentik provider)
|
|
7. **Client Secret**: (from Authentik provider)
|
|
8. **OpenID Connect Auto Discovery URL**: `https://auth.jnss.me/application/o/gitea/.well-known/openid-configuration`
|
|
9. **Additional Scopes**: `profile email groups`
|
|
10. Enable: **Skip local 2FA**, **Automatically create users**
|
|
|
|
**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"]
|
|
```
|
|
|
|
**Result**: Users see "Sign in with Authentik" button on Gitea login page with full OAuth flow.
|
|
|
|
### 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 |