14 KiB
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):
# 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:
- Database settings (via Admin panel) — Highest priority
- Environment variables — Fallback if DB value is blank
- 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:
- User is found or created by
external_subject(from OIDCsubclaim) - Email matching — If no user by
sub, check for existing user with same email (ifemail_verified=true) - Provisioning — If
OIDC_AUTO_PROVISION=true, create new user with:- Role:
adminif in configured admin group, elseuser - Password: empty (cannot use local password login)
auth_provider:oidcexternal_subject: OIDCsubclaim
- Role:
Group → Role Mapping
// 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:
- Client generates
code_verifier(random string) - Client creates
code_challenge = SHA256(code_verifier) - Authorization request includes
code_challenge - Token exchange includes
code_verifier - Server verifies
SHA256(code_verifier) == code_challenge
State Parameter
Prevents CSRF on OAuth flow:
- Random
stateis generated and stored in DB (5-minute TTL) - User redirected to Authentik with
stateparameter - On callback, state is validated and immediately consumed (prevents replay)
- 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()returnsfalse
Check:
- Verify all required environment variables are set
- Check Admin panel → OIDC configuration
- Verify
oidc_login_enabledsetting istrue - 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:
- Verify
OIDC_ISSUER_URLpoints to the issuer, not/authorize/endpoint - Test issuer discovery:
curl -v <OIDC_ISSUER_URL>/.well-known/openid-configuration - Check network connectivity from server to Authentik
- 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:
- Clear browser cookies (including Authentik session)
- Ensure only one Authentik login flow is active per browser
- 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:
- Authentik must send
email_verified=truein claims - Existing local user must have
auth_provider='local' - 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 Authentikstate: State parameter for CSRF protectionerror(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 enabledoidc_error=authorization_failed: Authentik erroroidc_error=invalid_callback: Missing code or stateoidc_error=invalid_or_expired_state: State mismatch/expiredoidc_error=authentication_failed: Token validation failedoidc_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:
- Discover OIDC metadata from issuer URL
- Verify authorization, token, and JWKS endpoints exist
- 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:
const CLIENT_CACHE_TTL = 60 * 60 * 1000; // 1 hour
When Authentik rotates keys (via JWKS), the next token exchange will:
- Detect cache expiration
- Re-discover OIDC provider
- Fetch new JWKS
- 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:
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— OIDC Express routesservices/oidcService.js— OIDC service logicmiddleware/csrf.js— CSRF middleware for OIDC- openid-client@5 Documentation
- OWASP OIDC Cheat Sheet