* fix: resolve OIDC session loss and migration failures This commit fixes three critical issues affecting OIDC/SSO authentication: 1. Session Not Saved Before Redirect - Added explicit req.session.save() callback in OIDC callback handler - Ensures session is persisted before redirecting to /today - Prevents 401 errors after successful SSO authentication 2. Migration Resilience - Added DROP TABLE IF EXISTS users_new in migration - Prevents "table already exists" errors from failed migrations - Created cleanup script for orphaned migration tables 3. Trust Proxy Documentation - Documented TUDUDI_TRUST_PROXY requirement for reverse proxy deployments - Added troubleshooting guide for session loss issues - Updated .env.example with OIDC configuration examples Fixes session loss when deployed behind reverse proxies (nginx, Traefik, etc.) Changes: - backend/modules/oidc/controller.js: Add session.save() before redirect - backend/migrations/20260420000004-make-password-optional.js: Add DROP TABLE IF EXISTS - backend/scripts/cleanup-failed-migration.js: New cleanup utility - backend/.env.example: Add OIDC and trust proxy examples - docs/10-oidc-sso.md: Add trust proxy configuration and troubleshooting - docs/feature-plans/00-oidc-sso.md: Document required environment variables * fix: prettier formatting in cleanup script
22 KiB
OIDC/SSO Authentication
This guide explains how to configure and use OpenID Connect (OIDC) Single Sign-On (SSO) authentication in Tududi.
Related: User Management, Architecture Overview
Table of Contents
- Overview
- Why Use OIDC/SSO
- Supported Providers
- Configuration
- Provider Setup Guides
- User Features
- Advanced Topics
- Troubleshooting
- Security Considerations
Overview
OIDC (OpenID Connect) is a modern authentication protocol that allows users to sign in to Tududi using external identity providers like Google, Okta, Keycloak, or any OIDC-compliant service.
Key Features:
- Single Sign-On: Use your existing corporate or personal accounts
- Just-In-Time Provisioning: New users are automatically created on first login
- Account Linking: Connect multiple authentication methods to one account
- Hybrid Authentication: Choose between email/password or SSO login
- Multiple Providers: Support for multiple OIDC providers simultaneously
Why Use OIDC/SSO
For Enterprise Users:
- Centralized identity management
- Enforce corporate security policies
- Simplified user onboarding/offboarding
- Compliance with security standards
For Self-Hosters:
- Use existing authentication infrastructure (Keycloak, Authentik)
- Reduce password fatigue
- Leverage provider security features (2FA, security keys)
- Simplify family/team access management
For Individual Users:
- One-click login with Google, Microsoft, etc.
- No need to remember another password
- Automatic profile updates from provider
Supported Providers
Tududi supports any OIDC-compliant identity provider, including:
| Provider | Type | Typical Use Case |
|---|---|---|
| Public | Personal accounts, G Suite | |
| Okta | Enterprise | Corporate SSO |
| Keycloak | Self-hosted | Open-source identity management |
| Authentik | Self-hosted | Homelab, small business |
| PocketID | Public | Decentralized identity |
| Azure AD | Enterprise | Microsoft 365 organizations |
| Generic OIDC | Any | Custom providers with .well-known/openid-configuration |
Configuration
OIDC providers are configured via environment variables in your .env file. After making changes, restart the Tududi server for them to take effect.
Single Provider Setup
For most users, a single provider is sufficient:
# Enable OIDC
OIDC_ENABLED=true
# Provider Configuration
OIDC_PROVIDER_NAME=Google
OIDC_PROVIDER_SLUG=google
OIDC_ISSUER_URL=https://accounts.google.com
OIDC_CLIENT_ID=your-client-id.apps.googleusercontent.com
OIDC_CLIENT_SECRET=your-client-secret
OIDC_SCOPE=openid profile email
# Auto-provisioning (recommended)
OIDC_AUTO_PROVISION=true
# Optional: Auto-assign admin role to specific email domains
OIDC_ADMIN_EMAIL_DOMAINS=example.com,mycompany.com
Required Variables:
OIDC_PROVIDER_NAME: Display name shown to users (e.g., "Google", "Company SSO")OIDC_PROVIDER_SLUG: URL-safe identifier (e.g., "google", "okta")OIDC_ISSUER_URL: Provider's OIDC discovery URLOIDC_CLIENT_ID: OAuth 2.0 client ID from providerOIDC_CLIENT_SECRET: OAuth 2.0 client secret from provider
Multiple Providers Setup
To support multiple providers, use numbered environment variables:
# Enable OIDC
OIDC_ENABLED=true
# Provider 1: Google
OIDC_PROVIDER_1_NAME=Google
OIDC_PROVIDER_1_SLUG=google
OIDC_PROVIDER_1_ISSUER=https://accounts.google.com
OIDC_PROVIDER_1_CLIENT_ID=xxx.apps.googleusercontent.com
OIDC_PROVIDER_1_CLIENT_SECRET=xxx
OIDC_PROVIDER_1_SCOPE=openid profile email
OIDC_PROVIDER_1_AUTO_PROVISION=true
# Provider 2: Company Okta
OIDC_PROVIDER_2_NAME=Company SSO
OIDC_PROVIDER_2_SLUG=okta
OIDC_PROVIDER_2_ISSUER=https://company.okta.com
OIDC_PROVIDER_2_CLIENT_ID=yyy
OIDC_PROVIDER_2_CLIENT_SECRET=yyy
OIDC_PROVIDER_2_AUTO_PROVISION=true
OIDC_PROVIDER_2_ADMIN_EMAIL_DOMAINS=company.com
# Provider 3: Self-hosted Authentik
OIDC_PROVIDER_3_NAME=Authentik
OIDC_PROVIDER_3_SLUG=authentik
OIDC_PROVIDER_3_ISSUER=https://auth.example.com/application/o/tududi/
OIDC_PROVIDER_3_CLIENT_ID=zzz
OIDC_PROVIDER_3_CLIENT_SECRET=zzz
OIDC_PROVIDER_3_AUTO_PROVISION=true
Numbering Rules:
- Start at
OIDC_PROVIDER_1_*, increment sequentially - No gaps allowed (1, 2, 3... not 1, 3, 5)
- Maximum: Practical limit ~5 providers (no hard limit)
Environment Variables Reference
| Variable | Required | Default | Description |
|---|---|---|---|
OIDC_ENABLED |
Yes | false |
Enable/disable OIDC feature |
OIDC_PROVIDER_NAME |
Yes | - | Provider display name |
OIDC_PROVIDER_SLUG |
Yes | - | URL-safe identifier |
OIDC_ISSUER_URL |
Yes | - | OIDC discovery endpoint |
OIDC_CLIENT_ID |
Yes | - | OAuth client ID |
OIDC_CLIENT_SECRET |
Yes | - | OAuth client secret |
OIDC_SCOPE |
No | openid profile email |
OAuth scopes |
OIDC_AUTO_PROVISION |
No | true |
Auto-create users on first login |
OIDC_ADMIN_EMAIL_DOMAINS |
No | - | Comma-separated domains for auto-admin |
BASE_URL |
Yes | - | Tududi base URL (for OAuth callbacks) |
Important: The BASE_URL variable must be set for OAuth redirects to work:
BASE_URL=http://localhost:3002 # Development
BASE_URL=https://tududi.example.com # Production
Trust Proxy Configuration (Required for Production):
If Tududi is deployed behind a reverse proxy (nginx, Traefik, Apache, etc.), you must configure Express to trust the proxy:
TUDUDI_TRUST_PROXY=true
This is required for:
- Proper session handling after OIDC login
- Rate limiting based on actual client IP addresses
- Correct IP logging in audit trails
Without this setting, you may experience:
- Session loss after SSO login (401 errors)
- Rate limiter errors:
ValidationError: The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false
Provider Setup Guides
1. Create OAuth 2.0 Credentials
- Go to Google Cloud Console
- Create a new project or select existing
- Navigate to APIs & Services > Credentials
- Click Create Credentials > OAuth client ID
- Select Web application
- Add authorized redirect URIs:
- Development:
http://localhost:3002/api/oidc/callback/google - Production:
https://your-domain.com/api/oidc/callback/google
- Development:
- Copy Client ID and Client Secret
2. Configure Tududi
OIDC_ENABLED=true
OIDC_PROVIDER_NAME=Google
OIDC_PROVIDER_SLUG=google
OIDC_ISSUER_URL=https://accounts.google.com
OIDC_CLIENT_ID=123456789.apps.googleusercontent.com
OIDC_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxx
OIDC_SCOPE=openid profile email
OIDC_AUTO_PROVISION=true
3. Test
- Restart Tududi
- Navigate to login page
- Click "Sign in with Google"
- Approve permissions
- You should be logged in!
Okta
1. Create OIDC Application
- Log in to your Okta admin console
- Go to Applications > Applications
- Click Create App Integration
- Select OIDC - OpenID Connect
- Select Web Application
- Configure:
- Sign-in redirect URIs:
https://your-domain.com/api/oidc/callback/okta - Sign-out redirect URIs:
https://your-domain.com/login - Controlled access: Choose your access policy
- Sign-in redirect URIs:
- Save and note the Client ID and Client Secret
2. Find Your Issuer URL
Format: https://{your-domain}.okta.com
Example: https://company.okta.com
3. Configure Tududi
OIDC_ENABLED=true
OIDC_PROVIDER_NAME=Company SSO
OIDC_PROVIDER_SLUG=okta
OIDC_ISSUER_URL=https://company.okta.com
OIDC_CLIENT_ID=0oa123456789abcde
OIDC_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxx
OIDC_SCOPE=openid profile email
OIDC_AUTO_PROVISION=true
OIDC_ADMIN_EMAIL_DOMAINS=company.com
Keycloak
1. Create OIDC Client
- Log in to Keycloak admin console
- Select your realm
- Go to Clients > Create client
- Configure:
- Client type: OpenID Connect
- Client ID:
tududi - Client authentication: ON (confidential)
- Valid redirect URIs:
https://your-domain.com/api/oidc/callback/keycloak
- Go to Credentials tab and copy Client secret
2. Find Your Issuer URL
Format: https://{keycloak-domain}/realms/{realm-name}
Example: https://auth.example.com/realms/myrealm
3. Configure Tududi
OIDC_ENABLED=true
OIDC_PROVIDER_NAME=Keycloak
OIDC_PROVIDER_SLUG=keycloak
OIDC_ISSUER_URL=https://auth.example.com/realms/myrealm
OIDC_CLIENT_ID=tududi
OIDC_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxx
OIDC_SCOPE=openid profile email
OIDC_AUTO_PROVISION=true
Authentik
1. Create OAuth2/OIDC Provider
- Log in to Authentik admin interface
- Go to Applications > Providers
- Click Create and select OAuth2/OpenID Provider
- Configure:
- Name: Tududi
- Authorization flow: Choose your flow
- Redirect URIs:
https://your-domain.com/api/oidc/callback/authentik - Signing Key: Select a certificate
- Note the Client ID and Client Secret
2. Create Application
- Go to Applications > Applications
- Click Create
- Link the provider you just created
- Configure slug and other settings
3. Find Your Issuer URL
Format: https://{authentik-domain}/application/o/{application-slug}/
Example: https://auth.example.com/application/o/tududi/
4. Configure Tududi
OIDC_ENABLED=true
OIDC_PROVIDER_NAME=Authentik
OIDC_PROVIDER_SLUG=authentik
OIDC_ISSUER_URL=https://auth.example.com/application/o/tududi/
OIDC_CLIENT_ID=xxxxxxxxxxxxxxxxxxxx
OIDC_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxx
OIDC_SCOPE=openid profile email
OIDC_AUTO_PROVISION=true
PocketID
1. Register Application
- Go to PocketID Developer Console
- Create a new application
- Configure:
- Name: Tududi
- Redirect URI:
https://your-domain.com/api/oidc/callback/pocketid
- Note the Client ID and Client Secret
2. Configure Tududi
OIDC_ENABLED=true
OIDC_PROVIDER_NAME=PocketID
OIDC_PROVIDER_SLUG=pocketid
OIDC_ISSUER_URL=https://pocketid.app
OIDC_CLIENT_ID=xxxxxxxxxxxxxxxxxxxx
OIDC_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxx
OIDC_SCOPE=openid profile email
OIDC_AUTO_PROVISION=true
Azure AD
1. Register Application
- Go to Azure Portal
- Navigate to Azure Active Directory > App registrations
- Click New registration
- Configure:
- Name: Tududi
- Supported account types: Choose your option
- Redirect URI: Web -
https://your-domain.com/api/oidc/callback/azure
- After creation, go to Certificates & secrets
- Create a new Client secret and copy it
- Note the Application (client) ID
2. Find Your Tenant ID
Go to Azure Active Directory > Overview and copy the Tenant ID
3. Configure Tududi
OIDC_ENABLED=true
OIDC_PROVIDER_NAME=Microsoft
OIDC_PROVIDER_SLUG=azure
OIDC_ISSUER_URL=https://login.microsoftonline.com/{tenant-id}/v2.0
OIDC_CLIENT_ID=12345678-1234-1234-1234-123456789012
OIDC_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxx
OIDC_SCOPE=openid profile email
OIDC_AUTO_PROVISION=true
Replace {tenant-id} with your actual tenant ID.
User Features
Logging In with SSO
First-Time Users:
- Navigate to Tududi login page
- Click the provider button (e.g., "Sign in with Google")
- You'll be redirected to the provider's login page
- Approve the requested permissions
- You'll be redirected back to Tududi and logged in
- A new account is automatically created (if auto-provisioning is enabled)
Returning Users:
- Click your provider button on the login page
- If already logged in to provider, you'll be immediately authenticated
- Redirected to the Today page
Account Linking
Users with existing email/password accounts can link SSO providers:
Steps:
- Log in with email/password
- Go to Profile > Security tab
- Scroll to Connected Accounts section
- Click Link [Provider Name]
- Approve permissions at provider
- Provider is now linked to your account
Benefits:
- Log in with either email/password OR SSO
- Switch between auth methods freely
- Maintain single account with multiple login options
Managing Connected Accounts
View Connected Accounts:
Go to Profile > Security > Connected Accounts to see:
- Linked providers
- Email addresses from each provider
- Date first linked
- Last login date
Unlink Account:
- Click Unlink next to the provider
- Confirm the action
Important: You cannot unlink your last authentication method. You must have either:
- A password set, OR
- At least one OIDC identity linked
Advanced Topics
Auto-Provisioning
When OIDC_AUTO_PROVISION=true (default), new users are automatically created on first login.
How It Works:
- User completes SSO login
- Tududi checks if an OIDC identity exists for this provider + user ID
- If not, checks if a user with the email exists:
- User exists: Links OIDC identity to existing user
- User doesn't exist: Creates new user with:
- Email from OIDC claims (verified)
- Username from email prefix
- No password (OIDC-only account)
- Optional admin role (if domain matches)
- User is logged in
Disable Auto-Provisioning:
OIDC_AUTO_PROVISION=false
When disabled:
- Only users with pre-linked OIDC identities can log in
- New SSO users are rejected with an error
- Useful for invite-only deployments
Admin Role Assignment
Automatically grant admin privileges based on email domain:
OIDC_ADMIN_EMAIL_DOMAINS=company.com,example.org
Rules:
- New users with emails from these domains become admins
- Applies only on first provisioning (not on subsequent logins)
- Existing non-admin users are not promoted
- Case-insensitive domain matching
Use Cases:
- Corporate deployments: Trust internal email domains
- Family instances: Trust your domain
- Multi-tenant: Different providers for different admin groups
Hybrid Authentication
Tududi supports hybrid authentication where users choose their preferred method:
Scenarios:
- Email/Password Only: Traditional authentication
- SSO Only: OIDC-only users (no password set)
- Both: Users can use either method
For OIDC-Only Users:
If a user was created via SSO and has no password:
- Attempting email/password login shows: "This account uses SSO. Please sign in with your SSO provider."
- User must log in via SSO or set a password via password reset
For Email/Password Users:
- Can link SSO providers at any time
- Both auth methods work independently
- Unlinking SSO doesn't affect password login
Troubleshooting
Session Lost After SSO Login (401 Errors)
Symptoms:
- Successfully authenticate with SSO provider
- Redirected to
/todaypage - Immediately see login screen again
- API calls return 401 "Authentication required"
- Browser logs show repeated 401 errors on
/api/profile
Cause: Express is not configured to trust the reverse proxy, causing session cookies to not be set properly.
Solution:
Add to your .env file:
TUDUDI_TRUST_PROXY=true
Then restart the Tududi server:
docker compose restart # For Docker
npm start # For standalone
Verification:
After restarting, the error log should no longer show:
ValidationError: The 'X-Forwarded-For' header is set but the Express 'trust proxy' setting is false
"Provider not found" Error
Cause: The provider slug in the URL doesn't match any configured provider.
Solution:
- Check
.envfile for correctOIDC_PROVIDER_SLUGvalue - Ensure slug is URL-safe (lowercase, no spaces)
- Restart server after
.envchanges
"Invalid state parameter" Error
Cause: OAuth state validation failed (security check).
Possible Reasons:
- State expired (>10 minutes old)
- Callback URL mismatch
- State already consumed
Solution:
- Start the login flow again (don't reuse old URLs)
- Check
BASE_URLmatches your actual domain - Verify callback URL in provider settings
"Auto-provisioning disabled" Error
Cause: User doesn't exist and OIDC_AUTO_PROVISION=false.
Solution:
- Enable auto-provisioning:
OIDC_AUTO_PROVISION=true, OR - Create user account manually first, then link SSO
Provider Button Not Showing
Cause: Provider not loaded from .env.
Solution:
- Check
OIDC_ENABLED=trueis set - Verify all required variables are present
- Check for typos in variable names
- Restart server
- Check browser console for API errors
"Invalid grant" or Token Errors
Cause: JWT validation failed.
Possible Reasons:
- Wrong client secret
- Clock skew between servers
- Issuer URL mismatch
Solution:
- Verify
OIDC_CLIENT_SECRETmatches provider - Ensure server time is accurate (NTP sync)
- Check
OIDC_ISSUER_URLexactly matches provider's issuer claim
Callback URL Mismatch
Cause: Redirect URI configured in provider doesn't match Tududi's callback.
Solution:
- Callback URL format:
{BASE_URL}/api/oidc/callback/{slug} - Example:
https://tududi.example.com/api/oidc/callback/google - Must match exactly in provider settings (including http/https)
- Update provider settings and restart Tududi
Can't Unlink Last Auth Method
Cause: Safety check prevents losing all access.
Solution:
- Set a password first (Profile > Security)
- Then unlink OIDC identity, OR
- Link another OIDC provider first
Security Considerations
Secret Storage
- Client secrets are stored in
.envfile (plaintext) - Ensure
.envis never committed to version control (already in.gitignore) - Use proper file permissions:
chmod 600 .envon Linux/macOS - For production, consider Docker secrets or Kubernetes secrets
OAuth Flow Security
Tududi implements standard OAuth 2.0 security measures:
- CSRF Protection: Cryptographically random state parameter (32 bytes)
- Replay Protection: State is one-time use, 10-minute TTL
- JWT Validation: ID tokens verified against provider's JWKS
- Nonce Validation: Prevents token reuse attacks
- TLS Enforcement: Always use HTTPS in production
Data Privacy
What's Stored:
- OIDC subject (provider's user ID)
- Email, name, profile picture from claims
- Full raw claims (JSON) for debugging
- First/last login timestamps
What's Not Stored:
- Provider passwords
- OAuth access tokens (discarded after login)
- Refresh tokens
Audit Trail
All authentication events are logged (if audit logging is enabled):
- Login success/failure
- OIDC linking/unlinking
- Provider information
- IP address and user agent
Check logs at: /backend/logs/ (if enabled)
Rate Limiting
OIDC endpoints are protected by rate limiting:
/api/oidc/auth/*: 5 requests per 15 minutes per IP/api/oidc/callback/*: 5 requests per 15 minutes per IP- Linking/unlinking: Standard authenticated API limits
Best Practices
- Use HTTPS: Always use HTTPS in production
- Restrict Callback URLs: Only whitelist exact callback URLs needed
- Rotate Secrets: Periodically rotate client secrets
- Monitor Logs: Watch for suspicious authentication attempts
- Limit Providers: Only enable providers you trust
- Email Verification: Trust provider's email verification
- Review Permissions: Only request necessary OAuth scopes
Migration from Email/Password
Existing deployments can gradually adopt OIDC:
Step 1: Configure Providers
Add OIDC configuration to .env without removing email/password support.
Step 2: Notify Users
Announce new SSO option to users.
Step 3: Users Link Accounts
Existing users can link SSO providers to their accounts via Profile > Security.
Step 4: Optional - Disable Email/Password
Not recommended, but possible by customizing the frontend Login component.
Rollback:
Simply set OIDC_ENABLED=false and restart. Email/password authentication continues to work.
API Integration
Fetch Available Providers:
GET /api/oidc/providers
Response:
[
{
"slug": "google",
"name": "Google",
"button_text": "Sign in with {name}",
"type": "oidc"
}
]
Initiate Login Flow:
Redirect user to:
GET /api/oidc/auth/{slug}
User's Connected Identities:
GET /api/oidc/identities
Authorization: Bearer <token>
See Swagger API docs for full API reference.
Support
Issues: GitHub Issues Discussions: GitHub Discussions Discord: Join our community
Related Documentation:
Document Version: 1.0.0 Last Updated: 2026-04-20 Maintainer: Update when OIDC features change