BillTracker/docs/Authentik-Integration.md

407 lines
14 KiB
Markdown

# Authentik OIDC Integration Guide
## Overview
This document describes how Authentik (or any OIDC-compatible identity provider) is integrated with the Bill Tracker application for single sign-on (SSO) authentication.
## Architecture
### Components
| Component | File | Purpose |
|-----------|------|---------|
| **OIDC Routes** | `routes/authOidc.js` | Express routes for login initiation and callback handling |
| **OIDC Service** | `services/oidcService.js` | Core OIDC logic: token validation, user provisioning, group mapping |
| **CSRF Middleware** | `middleware/csrf.js` | CSRF protection for OIDC endpoints |
| **Auth Service** | `services/authService.js` | Session creation and management |
### Data Flow
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Browser │ │ Bill │ │ Authentik │
│ │ │ Tracker │ │ (IdP) │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ Click "Login" │ │
│───────────────────────>│ │
│ │ │
│ │ GET /api/auth/oidc/login │
│ │───────────────────────>│
│ │ │
│ │ │ Generate PKCE + State
│ │ │ Store in DB with TTL
│ │ │
│ │ │ Redirect to Authentik
│ │<───────────────────────│
│ │ │
│ │ 302 Redirect │
│ │───────────────────────>│
│ │ │
│ │ │ User authenticates
│ │ │
│ │ │ Redirect back with code
│ │ │ GET /api/auth/oidc/callback?code=...
│ │<───────────────────────│
│ │ │
│ │ Exchange code for tokens │
│ │ Verify ID token (JWKS) │
│ │ Validate state/PKCE │
│ │ │
│ │ Find/create user │
│ │ │
│ │ Create local session │
│ │ Set session cookie │
│ │ │
│ │ 302 Redirect to / │
│<───────────────────────│ │
│ │ │
│ Session cookie │ │
│ Authenticated │ │
│ │ │
```
## Environment Configuration
Add these to your `.env` file (or configure via Admin panel):
```bash
# OIDC Enabled
OIDC_ENABLED=true
# Authentik Provider Details
OIDC_ISSUER_URL=https://auth.yourdomain.com/application/o/bill-tracker/
OIDC_CLIENT_ID=your-client-id-from-authentik
OIDC_CLIENT_SECRET=your-client-secret-from-authentik
OIDC_REDIRECT_URI=https://bills.yourdomain.com/api/auth/oidc/callback
# Scopes to request
OIDC_SCOPES="openid email profile groups"
# Admin Group Mapping
OIDC_ADMIN_GROUP=bill-tracker-admins
# Optional
OIDC_AUTO_PROVISION=true
OIDC_DEFAULT_ROLE=user
OIDC_PROVIDER_NAME=authentik
```
## Authentik Setup
### Step 1: Create OAuth2/OpenID Provider
In Authentik, navigate to **Providers****Create**:
| Setting | Value |
|---------|-------|
| Name | `bill-tracker` |
| Client Type | `Confidential` |
| Authorization Flow | `Explicit` |
| Redirect URIs | `https://bills.yourdomain.com/api/auth/oidc/callback` |
| Scopes | `openid`, `email`, `profile`, `groups` |
### Step 2: Create Application
In Authentik, navigate to **Applications****Applications****Create**:
| Setting | Value |
|---------|-------|
| Name | `bill-tracker` |
| Provider | Select the `bill-tracker` provider created above |
| Slug | `bill-tracker` |
| Path | `/` (or appropriate path) |
### Step 3: Assign Users
Assign users or groups to the `bill-tracker` application in Authentik.
## Configuration Options
### Priority Order
Configuration is resolved in this order:
1. **Database settings** (via Admin panel) — Highest priority
2. **Environment variables** — Fallback if DB value is blank
3. **Safe defaults** — If neither DB nor env is set
### Required Settings
| Setting | Env Variable | Description |
|---------|--------------|-------------|
| Issuer URL | `OIDC_ISSUER_URL` | Authentik application issuer URL (includes `/application/o/` path) |
| Client ID | `OIDC_CLIENT_ID` | Client ID from Authentik application |
| Client Secret | `OIDC_CLIENT_SECRET` | Client secret from Authentik application |
| Redirect URI | `OIDC_REDIRECT_URI` | Must exactly match Authentik redirect URI |
### Optional Settings
| Setting | Env Variable | Default | Description |
|---------|--------------|---------|-------------|
| Scopes | `OIDC_SCOPES` | `openid email profile groups` | Space-separated list of OAuth2 scopes |
| Admin Group | `OIDC_ADMIN_GROUP` | (none) | Authentik group name whose members get admin role |
| Auto Provision | `OIDC_AUTO_PROVISION` | `true` | Auto-create users if they don't exist |
| Default Role | (DB only) | `user` | Role for non-admin users |
| Token Auth Method | `OIDC_TOKEN_AUTH_METHOD` | `client_secret_basic` | How client authenticates to token endpoint |
## User Provisioning
### Auto-Provision Flow
When a user logs in via OIDC and doesn't exist locally:
1. **User is found or created** by `external_subject` (from OIDC `sub` claim)
2. **Email matching** — If no user by `sub`, check for existing user with same email (if `email_verified=true`)
3. **Provisioning** — If `OIDC_AUTO_PROVISION=true`, create new user with:
- Role: `admin` if in configured admin group, else `user`
- Password: empty (cannot use local password login)
- `auth_provider`: `oidc`
- `external_subject`: OIDC `sub` claim
### Group → Role Mapping
```javascript
// Pseudocode
function mapRoleFromClaims(claims, config) {
const adminGroup = config.adminGroup;
const groups = claims.groups || [];
if (!adminGroup) return 'user';
if (Array.isArray(groups) && groups.includes(adminGroup)) {
return 'admin';
}
return 'user';
}
```
## Security Features
### PKCE (Proof Key for Code Exchange)
Prevents code interception attacks:
1. Client generates `code_verifier` (random string)
2. Client creates `code_challenge = SHA256(code_verifier)`
3. Authorization request includes `code_challenge`
4. Token exchange includes `code_verifier`
5. Server verifies `SHA256(code_verifier) == code_challenge`
### State Parameter
Prevents CSRF on OAuth flow:
1. Random `state` is generated and stored in DB (5-minute TTL)
2. User redirected to Authentik with `state` parameter
3. On callback, state is validated and immediately consumed (prevents replay)
4. If state doesn't match or is expired → redirect with error
### ID Token Verification
Using `openid-client@5`, the following validations are performed:
| Check | Purpose |
|-------|---------|
| JWT signature via JWKS | Cryptographic verification of issuer |
| Issuer (`iss`) | Must match configured issuer URL |
| Audience (`aud`) | Must include client ID |
| Expiry (`exp`) | Token must not be expired |
| Not-before (`nbf`) | Token must not be used before date |
| Nonce | Prevents replay attacks |
| `sub` claim | User identifier must be present |
### Security Best Practices
- Tokens and codes are **never logged**
- Client secret is **never exposed** to frontend
- State tokens are **consumed immediately** (one-time use)
- Session cookies use same security settings as local login
- Admin group mapping requires explicit group membership
## Troubleshooting
### Issue: "OIDC authentication is not configured"
**Symptoms:**
- Login redirects return 501
- `isOidcLoginActive()` returns `false`
**Check:**
1. Verify all required environment variables are set
2. Check Admin panel → OIDC configuration
3. Verify `oidc_login_enabled` setting is `true`
4. Test configuration via Admin panel → "Test Configuration" button
### Issue: "Failed to reach the identity provider"
**Symptoms:**
- Login redirects return 502
- Network error in server logs
**Check:**
1. Verify `OIDC_ISSUER_URL` points to the **issuer**, not `/authorize/` endpoint
2. Test issuer discovery: `curl -v <OIDC_ISSUER_URL>/.well-known/openid-configuration`
3. Check network connectivity from server to Authentik
4. Verify TLS/SSL certificates are valid
### Issue: "Invalid or expired state"
**Symptoms:**
- Callback redirects with `oidc_error=invalid_or_expired_state`
- State parameter mismatch
**Check:**
1. Clear browser cookies (including Authentik session)
2. Ensure only one Authentik login flow is active per browser
3. Check server logs for state creation/consumption timing
### Issue: "access_denied" or "authentication_failed"
**Symptoms:**
- Callback redirects with error query parameter
- User redirected to login with error message
**Common causes:**
- User not assigned to application in Authentik
- Groups claim missing expected admin group
- Token expired before exchange
- PKCE validation failed (replay attempt)
### Issue: Email not linking to existing account
**Symptoms:**
- New user created instead of linking existing local account
**Check:**
1. Authentik must send `email_verified=true` in claims
2. Existing local user must have `auth_provider='local'`
3. Only first match is linked (create one local account per email)
## API Endpoints
### GET /api/auth/oidc/login
Initiates OIDC login flow.
**Query Parameters:**
- `redirect_to` (optional): Path to redirect after successful login
**Behavior:**
- If OIDC not configured → returns 501
- Redirects to Authentik authorization endpoint
- State stored in DB with 5-minute TTL
**Success Response:** 302 Redirect to Authentik
### GET /api/auth/oidc/callback
Handles redirect from Authentik after user authentication.
**Query Parameters:**
- `code`: Authorization code from Authentik
- `state`: State parameter for CSRF protection
- `error` (optional): Provider error code
**Behavior:**
- Validates and consumes state
- Exchanges code for tokens
- Verifies ID token (signature, claims)
- Finds or creates local user
- Creates local session
- Sets session cookie
- Redirects to frontend
**Error Redirects:**
- `oidc_error=not_configured`: OIDC not enabled
- `oidc_error=authorization_failed`: Authentik error
- `oidc_error=invalid_callback`: Missing code or state
- `oidc_error=invalid_or_expired_state`: State mismatch/expired
- `oidc_error=authentication_failed`: Token validation failed
- `oidc_error=access_denied`: User denied access
**Success Response:** 302 Redirect to `/` or `redirect_to` path
## Admin Panel
### OIDC Settings Location
**Admin Panel****Authentication****OIDC Settings**
### Settings Form Fields
| Field | Source | Description |
|-------|--------|-------------|
| Provider Name | Env/Default | Display name for login button |
| Issuer URL | Env/DB | Authentik issuer URL |
| Client ID | Env/DB | OAuth2 client ID |
| Client Secret | Env/DB | OAuth2 client secret (masked) |
| Token Auth Method | Env/DB | `client_secret_basic` or `client_secret_post` |
| Redirect URI | Auto | Must match Authentik (auto-populated) |
| Scopes | Env/DB | Space-separated scopes |
| Admin Group | Env/DB | Authentik group name for admin role |
| Auto Provision | Env/DB | Create users automatically |
| Enabled | DB only | Toggle OIDC login |
### Testing Configuration
Click **"Test Configuration"** to:
1. Discover OIDC metadata from issuer URL
2. Verify authorization, token, and JWKS endpoints exist
3. Validate client credentials
**Response includes:**
- Configuration status (ok/error)
- Missing fields if error
- Provider metadata (issuer, scopes, etc.)
## Advanced Topics
### JWKS Key Rotation
The OIDC client cache has a 1-hour TTL:
```javascript
const CLIENT_CACHE_TTL = 60 * 60 * 1000; // 1 hour
```
When Authentik rotates keys (via JWKS), the next token exchange will:
1. Detect cache expiration
2. Re-discover OIDC provider
3. Fetch new JWKS
4. Verify token signature with new key
### Multiple OIDC Providers
Not currently supported. The system uses a single OIDC configuration. Workarounds:
- Use Authentik as a single identity provider aggregating multiple backends
- Deploy separate instances per provider
### Custom Claim Mapping
To add custom role mapping, modify `mapRoleFromClaims()` in `services/oidcService.js`:
```javascript
function mapRoleFromClaims(claims, config) {
// Custom logic here
if (claims.your_custom_claim === 'value') {
return 'custom_role';
}
// Default logic
const adminGroup = config.adminGroup;
const groups = claims.groups || [];
return list.includes(adminGroup) ? 'admin' : 'user';
}
```
## References
- [`routes/authOidc.js`](../routes/authOidc.js) — OIDC Express routes
- [`services/oidcService.js`](../services/oidcService.js) — OIDC service logic
- [`middleware/csrf.js`](../middleware/csrf.js) — CSRF middleware for OIDC
- [openid-client@5 Documentation](https://github.com/panva/node-openid-client)
- [OWASP OIDC Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/OpenID_Connect_Cheat_Sheet.html)