- Add AUTH_METHOD switch (local | oidc | both); default remains local - Update login UI to conditionally show SSO button and/or local form - Add Authlib and initialize OAuth client (discovery-based) in app factory - Implement OIDC Authorization Code flow with PKCE: - GET /login/oidc → starts auth flow, preserves `next` - GET /auth/oidc/callback → exchanges code, parses ID token, fetches userinfo - Maps claims to username/full_name/email; admin mapping via group/email - Logs user in and redirects to intended page - Add optional OIDC end-session on logout (falls back gracefully if unsupported) - Extend User model with `email`, `oidc_issuer`, `oidc_sub` and unique constraint - Add Alembic migration 015 (adds columns, index, unique constraint) - Update env.example with OIDC variables and AUTH_METHOD - Add docs/OIDC_SETUP.md with provider-agnostic setup guide and examples - fix: remove invalid walrus usage in OIDC client registration Migration: - Run database migrations (e.g., `flask db upgrade`) to apply revision 015 Config: - AUTH_METHOD=local|oidc|both - OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_REDIRECT_URI - OIDC_SCOPES (default: "openid profile email") - OIDC_USERNAME_CLAIM, OIDC_FULL_NAME_CLAIM, OIDC_EMAIL_CLAIM, OIDC_GROUPS_CLAIM - OIDC_ADMIN_GROUP (optional), OIDC_ADMIN_EMAILS (optional) - OIDC_POST_LOGOUT_REDIRECT_URI (optional) Routes: - /login (respects AUTH_METHOD), /login/oidc, /auth/oidc/callback, /logout Docs: - See docs/OIDC_SETUP.md for full setup, provider notes, and troubleshooting
6.0 KiB
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) orAUTH_METHOD=both(SSO + local form). - Configure
OIDC_ISSUER,OIDC_CLIENT_ID,OIDC_CLIENT_SECRET, andOIDC_REDIRECT_URI. - Optional: Configure admin mapping via
OIDC_ADMIN_GROUPorOIDC_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://<your-app-host>/auth/oidc/callback
- Post-logout redirect (optional):
https://<your-app-host>/
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 logout behavior
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/<tenant-id>/v2.0 - Use
openid profile emailscopes. - Preferred username commonly available via
preferred_usernameorupn. - Group claims may need to be enabled in App Registration → Token configuration.
- Issuer:
-
Okta
- Issuer:
https://<yourOktaDomain>/oauth2/default - Add claims for
groupsif you want role mapping by group.
- Issuer:
-
Keycloak
- Issuer:
https://<keycloak>/realms/<realm> - You can map custom claims and groups in the realm client.
- Issuer:
-
Google Workspace
- Issuer:
https://accounts.google.com - Groups generally not available by default; prefer admin mapping via emails.
- Issuer:
4) Behavior and Mapping
- When a user completes SSO:
- We parse ID token and/or fetch userinfo to get
preferred_username,name,emailand optionalgroups. - We upsert a local user record with
username,full_name,email, and store OIDC linkage inoidc_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_GROUPor if user’s email is inOIDC_ADMIN_EMAILS.
- We parse ID token and/or fetch userinfo to get
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/loginredirects to SSO.both: show SSO button and keep local form.
6) Docker Compose Example
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=truein 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-Protoso redirects use HTTPS URLs.
8) Troubleshooting
-
“SSO button doesn’t appear”
- Check
AUTH_METHOD. Must beoidcorboth.
- Check
-
“Redirect URI mismatch”
- The
OIDC_REDIRECT_URImust exactly match the value registered at your IdP.
- The
-
“Invalid token / missing claims”
- Confirm scopes and claim names. Override with
OIDC_*_CLAIMenvs if your IdP uses different names.
- Confirm scopes and claim names. Override with
-
“User is not admin”
- Verify
OIDC_ADMIN_GROUPmatches the group claim value, or add the user’s email toOIDC_ADMIN_EMAILS.
- Verify
-
“Logout keeps me signed in”
- Not all IdPs support end-session. If supported, we redirect to the provider’s end-session endpoint with
post_logout_redirect_uri.
- Not all IdPs support end-session. If supported, we redirect to the provider’s end-session endpoint with
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.