Skip to content

Latest commit

 

History

History
133 lines (106 loc) · 5.88 KB

File metadata and controls

133 lines (106 loc) · 5.88 KB

Multi-Tenant OIDC Setup Guide

Updated: 2026-01-05 Audience: Platform & support engineers Goal: Let each tenant authenticate against its own IdP while keeping a single Eneo deployment.


When to Use This Guide

This guide applies when FEDERATION_PER_TENANT_ENABLED=true. Use this mode if:

  • Multi-tenant: Each tenant needs their own IdP (Entra ID, Okta, Auth0, etc.)
  • Single-tenant with API management: You want to manage OIDC via API instead of environment variables (changes take effect without restart)

Not using federation? For simple single-tenant deployments, keep FEDERATION_PER_TENANT_ENABLED=false and configure OIDC via environment variables (OIDC_DISCOVERY_ENDPOINT, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET). See the deployment templates.


1. Before You Start

  • FEDERATION_PER_TENANT_ENABLED=true in backend .env
  • Super admin API key available (needed for every /api/v1/sysadmin/... call)
  • Redis running (recommended for the debug toggle; file fallback works)
  • Each tenant has a slug (tenant.slug). Backfill once with:
    cd backend
    uv run python -m intric.cli.backfill_tenant_slugs

Terminology

  • tenant_id – UUID in admin APIs (e.g., 123e4567-e89b-12d3-a456-426614174000)
  • tenant slug – URL-safe label exposed to the frontend (e.g., examplea)

2. Configure a Tenant IdP (Example: MunicipalityExampleA using Entra ID)

  1. Provide a new federation config

    curl -X PUT "https://api.eneo.local/api/v1/sysadmin/tenants/{tenant_id}/federation" \
      -H "X-API-Key: {SUPER_API_KEY}" \
      -H "Content-Type: application/json" \
      -d '{
        "provider": "entra_id",
        "canonical_public_origin": "https://examplea.eneo.local",
        "discovery_endpoint": "https://login.microsoftonline.com/{azure-tenant-id}/v2.0/.well-known/openid-configuration",
        "client_id": "{azure-client-id}",
        "client_secret": "{azure-client-secret}",
        "allowed_domains": ["examplea.gov"],
        "scopes": ["openid", "email", "profile"]
      }'
  2. Register redirect URI with the IdP (must match canonical_public_origin):
    https://examplea.eneo.local/auth/callback

  3. Smoke test the configuration (dry run; no user interaction):

    curl -X POST \
      -H "X-API-Key: {SUPER_API_KEY}" \
      https://api.eneo.local/api/v1/sysadmin/tenants/{tenant_id}/federation/test

Repeat for each tenant (e.g., MunicipalityExampleB with Auth0, MunicipalityExampleC with Okta). Only the IdP-specific fields change.

To update the current setup later without resending every field, use PATCH /api/v1/sysadmin/tenants/{tenant_id}/federation. Omitted fields stay unchanged:

curl -X PATCH "https://api.eneo.local/api/v1/sysadmin/tenants/{tenant_id}/federation" \
  -H "X-API-Key: {SUPER_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "allowed_domains": ["examplea.gov", "examplea.se"]
  }'

Use PUT when you want to provide a full new federation definition or replace the current one.


3. Runtime Debugging (Correlation-ID Based)

Use this flow when a tenant reports “login failed”. Everything happens on the backend; no code redeploy needed.

3.1 Toggle verbose logging

curl -X POST https://api.eneo.local/api/v1/sysadmin/observability/oidc-debug/ \
  -H "X-API-Key: {SUPER_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true, "duration_minutes": 10, "reason": "Case #452"}'
  • Auto-expires after the requested duration (max 120 minutes)
  • Stored in Redis (observability:oidc_debug) or /app/data/debug_flags/oidc_debug.json fallback

Check status:
GET /api/v1/sysadmin/observability/oidc-debug/

3.2 Trace the correlation ID

Ask the user for the correlationId surfaced in the UI. Filter server logs:

journalctl -u backend.service -o cat | jq 'select(.correlation_id=="fa4fdb8fdbeee426")'
Breadcrumb (log message) Typical cause Next action
callback.domain_rejected Email domain not in tenant allowed_domains Add domain or ask user to use approved account
callback.user_missing Email not found in tenant user table Invite/import the user for that tenant
callback.user_tenant_mismatch User exists but belongs to another tenant Move user to correct tenant or adjust login link
callback.state_cache_error Redis unavailable during login Check Redis health; retry once cache is restored
initiate.state_cache_failed Redis write failed during initiate Investigate Redis/file permissions, then retry

Turn off logging once the incident is resolved:

curl -X POST https://api.eneo.local/api/v1/sysadmin/observability/oidc-debug/ \
  -H "X-API-Key: {SUPER_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"enabled": false, "reason": "Case #452 closed"}'

4. Support Playbook (TL;DR)

  1. Report comes in → enable debug toggle (10 min window)
  2. Reproduce or ask user to retry → capture correlationId
  3. Filter logs → identify breadcrumb from table above
  4. Fix config
    • Add tenant credential (/sysadmin/tenants/{id}/credentials/...) if LLM access is missing
    • Adjust federation settings (/federation endpoints)
  5. Disable toggle → confirm resolution with tenant

5. References