Files
TimeTracker/docs/OIDC_SETUP.md
Dries Peeters 04ed5ef8ae fix(oidc): only perform RP-Initiated Logout when OIDC_POST_LOGOUT_REDIRECT_URI is set
ixes #88

When OIDC_POST_LOGOUT_REDIRECT_URI was unset, the application was still
attempting RP-Initiated Logout by falling back to a generated redirect URL.
This caused issues with OIDC providers like Authelia that don't support
RP-Initiated Logout, resulting in failed redirects to unsupported endpoints.

Changes:
- Modified logout logic in app/routes/auth.py to only attempt provider
  logout when OIDC_POST_LOGOUT_REDIRECT_URI is explicitly configured
- If unset, users are now logged out locally and redirected to the
  TimeTracker login page (expected behavior)
- If set, RP-Initiated Logout proceeds as before (backward compatible)

Documentation:
- Updated docs/OIDC_SETUP.md with guidance on when to set the config
- Added clear comments in env.example explaining optional behavior
- Documented troubleshooting steps for providers without RP-Initiated
  Logout support (e.g., Authelia)

Tests:
- Added comprehensive test suite (tests/test_oidc_logout.py) with 9 tests
  covering different logout scenarios and edge cases
- All existing tests continue to pass (no regressions)

This change is fully backward compatible. Users with providers supporting
RP-Initiated Logout can continue using OIDC_POST_LOGOUT_REDIRECT_URI as
before. Users with providers like Authelia should leave it unset for
local-only logout.
2025-10-17 12:51:43 +02:00

6.5 KiB
Raw Blame History

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://<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: 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/<tenant-id>/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://<yourOktaDomain>/oauth2/default
    • Add claims for groups if you want role mapping by group.
  • Keycloak

    • Issuer: https://<keycloak>/realms/<realm>
    • 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 theyre blocked.
    • Admin role can be granted if users groups contains OIDC_ADMIN_GROUP or if users 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

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 doesnt 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 users 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 wasnt 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.