## OpenID Connect (OIDC) Setup Guide This guide explains how to enable Single Sign-On (SSO) with OpenID Connect for TimeTracker. OIDC is optional; you can run with local login only, OIDC only, or both. ### Quick Summary - Set `AUTH_METHOD=oidc` (SSO only) or `AUTH_METHOD=both` (SSO + local form). - Configure `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, and `OIDC_REDIRECT_URI`. - Optional: Configure admin mapping via `OIDC_ADMIN_GROUP` or `OIDC_ADMIN_EMAILS`. - Restart the app. The login page will show an “Sign in with SSO” button when enabled. ### Prerequisites - A running TimeTracker instance (Docker or local). - An OIDC provider (e.g., Azure AD, Okta, Keycloak, Auth0, Google Workspace). - A client application registered at your IdP with Authorization Code flow enabled. ### 1) Application URLs You will need these URLs when creating the OIDC client at your Identity Provider: - Authorization callback (Redirect URI): - `https:///auth/oidc/callback` - Post-logout redirect (optional): - `https:///` Make sure your external URL and protocol (HTTP/HTTPS) match how users access the app. Behind a reverse proxy, ensure the proxy sets `X-Forwarded-Proto` so redirects/cookies work correctly. ### 2) Required Environment Variables Add these to your environment (e.g., `.env`, Docker Compose, or Kubernetes Secrets): ``` AUTH_METHOD=oidc # or both, or local # Core OIDC settings OIDC_ISSUER=https://idp.example.com/realms/your-realm OIDC_CLIENT_ID=your-client-id OIDC_CLIENT_SECRET=your-client-secret OIDC_REDIRECT_URI=https://your-app.example.com/auth/oidc/callback # Scopes and claims (defaults are usually fine) OIDC_SCOPES=openid profile email OIDC_USERNAME_CLAIM=preferred_username OIDC_FULL_NAME_CLAIM=name OIDC_EMAIL_CLAIM=email OIDC_GROUPS_CLAIM=groups # Optional admin mapping OIDC_ADMIN_GROUP=timetracker-admins # If your IdP issues a groups claim OIDC_ADMIN_EMAILS=alice@company.com,bob@company.com # Optional: RP-Initiated Logout (set only if your provider supports end_session_endpoint) # If unset, users will be logged out locally and redirected to TimeTracker's login page. # If set, TimeTracker will redirect to the provider's logout endpoint after local logout. OIDC_POST_LOGOUT_REDIRECT_URI=https://your-app.example.com/ ``` Also ensure the standard app settings are configured (database, secret key, etc.). See `env.example` for a complete template. ### 3) Provider-Specific Notes - Azure AD (Entra ID) - Issuer: `https://login.microsoftonline.com//v2.0` - Use `openid profile email` scopes. - Preferred username commonly available via `preferred_username` or `upn`. - Group claims may need to be enabled in App Registration → Token configuration. - Okta - Issuer: `https:///oauth2/default` - Add claims for `groups` if you want role mapping by group. - Keycloak - Issuer: `https:///realms/` - You can map custom claims and groups in the realm client. - Google Workspace - Issuer: `https://accounts.google.com` - Groups generally not available by default; prefer admin mapping via emails. ### 4) Behavior and Mapping - When a user completes SSO: - We parse ID token and/or fetch userinfo to get `preferred_username`, `name`, `email` and optional `groups`. - We upsert a local user record with `username`, `full_name`, `email`, and store OIDC linkage in `oidc_issuer` + `oidc_sub`. - If `ALLOW_SELF_REGISTER=true` (default), unknown users are created on first login; otherwise they’re blocked. - Admin role can be granted if user’s groups contains `OIDC_ADMIN_GROUP` or if user’s email is in `OIDC_ADMIN_EMAILS`. ### 5) Local Login Coexistence `AUTH_METHOD` controls the login options: - `local`: username-only form (default, no SSO). - `oidc`: SSO only, local form is hidden and `/login` redirects to SSO. - `both`: show SSO button and keep local form. ### 6) Docker Compose Example ```yaml services: app: image: ghcr.io/your-org/timetracker:latest environment: - AUTH_METHOD=oidc - OIDC_ISSUER=https://idp.example.com/realms/your-realm - OIDC_CLIENT_ID=${OIDC_CLIENT_ID} - OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET} - OIDC_REDIRECT_URI=https://your-app.example.com/auth/oidc/callback - OIDC_SCOPES=openid profile email - OIDC_ADMIN_GROUP=timetracker-admins - OIDC_POST_LOGOUT_REDIRECT_URI=https://your-app.example.com/ - SECRET_KEY=${SECRET_KEY} - DATABASE_URL=${DATABASE_URL} # ... other settings like ports/volumes ``` ### 7) Security Recommendations - Always use HTTPS in production. - Set secure cookies: `SESSION_COOKIE_SECURE=true` in production. - Keep the client secret in a secret store (not committed to git). - Restrict `ADMIN_*` variables to trusted values only. - Ensure your reverse proxy forwards `X-Forwarded-Proto` so redirects use HTTPS URLs. ### 8) Troubleshooting - “SSO button doesn’t appear” - Check `AUTH_METHOD`. Must be `oidc` or `both`. - “Redirect URI mismatch” - The `OIDC_REDIRECT_URI` must exactly match the value registered at your IdP. - “Invalid token / missing claims” - Confirm scopes and claim names. Override with `OIDC_*_CLAIM` envs if your IdP uses different names. - “User is not admin” - Verify `OIDC_ADMIN_GROUP` matches the group claim value, or add the user’s email to `OIDC_ADMIN_EMAILS`. - "Logout keeps me signed in" or "Logout redirects to provider error page" - Not all IdPs support RP-Initiated Logout (end-session). If your provider doesn't support it (e.g., Authelia), **do not set** `OIDC_POST_LOGOUT_REDIRECT_URI`. TimeTracker will then perform local logout only and redirect to the login page. - If your provider supports end-session and you want to log out from the IdP too, set `OIDC_POST_LOGOUT_REDIRECT_URI` to your desired post-logout landing page. ### 9) Routes Reference - Local login page: `GET /login` (POST for username form when enabled) - Start OIDC login: `GET /login/oidc` - OIDC callback: `GET /auth/oidc/callback` - Logout: `GET /logout` (tries provider end-session if available) ### 10) Database Changes The app includes a migration that adds the following to `users`: - `email` (nullable) - `oidc_issuer` (nullable) - `oidc_sub` (nullable) - Unique constraint on `(oidc_issuer, oidc_sub)` If your DB wasn’t migrated automatically, run your usual migration flow. ### 11) Support If you run into issues, capture the application logs (including the IdP error page if any) and verify your env vars. Most problems are due to a mismatch in redirect URI, missing scopes/claims, or proxy/HTTPS configuration.