Configuring OIDC
Wiring an OIDC identity provider into Flux — issuer, audience, JWKS, signing algorithms, claim mapping, and clock skew.
Flux validates OIDC bearer tokens on every request: there is no session and no token cache. The provider fetches your IdP’s discovery document, pulls JWKS keys from it, verifies the JWT signature, and resolves the sub claim to a principal in Flux’s registry. This page walks through the configuration surface, one provider example, and the failure modes you will hit first.
Configuration fields
All fields live under [flux.security.auth.oidc]. Defaults come from flux/security/config.py::OIDCConfig.
| Field | Type | Default | Purpose |
|---|---|---|---|
enabled | bool | false | Turn the provider on. When true, issuer and audience are required at startup. |
issuer | str | "" | Your IdP’s issuer URL. Flux appends /.well-known/openid-configuration to it for discovery. |
audience | str | "" | The aud claim your IdP issues for this Flux server. Tokens with any other aud are rejected. |
jwks_cache_ttl | int | 3600 | Seconds to cache the discovery document and JWKS keys. |
clock_skew | int | 30 | Leeway in seconds for exp / nbf validation. |
default_user_roles | list[str] | [] | Roles assigned to a principal on auto-provisioning. Empty means auto-provisioning is off — unknown subjects get a Principal not provisioned 401. |
roles_claim | str | "roles" | Deprecated. Flux no longer reads roles from token claims; roles live in the principals registry. |
There is no jwks_url field. Flux always discovers the JWKS endpoint from the issuer’s .well-known/openid-configuration document and reads jwks_uri from it. Without discovery, OIDC cannot be configured.
Supported signing algorithms are RS256 and ES256. HS256 and other symmetric algorithms are rejected.
Enabling OIDC
Pick one of the two surfaces; they are equivalent and follow the same precedence as the rest of Flux config (env vars override flux.toml).
In flux.toml:
[flux.security.auth.oidc]
enabled = true
issuer = "https://login.example.com/realms/flux"
audience = "flux-server"
jwks_cache_ttl = 3600
clock_skew = 30
default_user_roles = ["viewer"]
Or via environment variables:
export FLUX_SECURITY__AUTH__OIDC__ENABLED=true
export FLUX_SECURITY__AUTH__OIDC__ISSUER=https://login.example.com/realms/flux
export FLUX_SECURITY__AUTH__OIDC__AUDIENCE=flux-server
export FLUX_SECURITY__AUTH__OIDC__JWKS_CACHE_TTL=3600
export FLUX_SECURITY__AUTH__OIDC__CLOCK_SKEW=30
export FLUX_SECURITY__AUTH__OIDC__DEFAULT_USER_ROLES='["viewer"]'
Enabling OIDC flips the derived auth.enabled flag to true automatically (it is computed as oidc.enabled or api_keys.enabled). You do not set it directly — anything written to [flux.security.auth] enabled is ignored.
Claim mapping
Flux reads four token fields directly: sub, iss, aud, and exp (all four are required). After signature validation, the provider resolves the principal by (sub, iss) against the principals registry.
| Token claim | Used for |
|---|---|
sub | Principal lookup key and the FluxIdentity.subject value. |
iss | Co-lookup key — supports two IdPs issuing the same sub. |
aud | Must equal the configured audience. |
exp, iat | Lifetime validation (with clock_skew leeway). |
name | Display name on the principal record. |
| everything else | Stored on the principal as opaque metadata, minus a small exclusion list (email, email_verified, iss, aud, exp, iat, jti, azp, nonce, at_hash, auth_time, session_state, acr, amr, realm_access, resource_access, typ). |
Roles are not pulled from the token. They are assigned in Flux via flux roles and flux principals assign-role. If default_user_roles is set, an unknown (sub, iss) is auto-provisioned with those roles on first sign-in; otherwise, the request fails with Principal not provisioned.
Example: Keycloak
On the Keycloak side, create a client (flux-server), set its access type appropriately for your front-end, and add an audience mapper so that issued tokens carry aud=flux-server. Confirm the realm signs with RS256 (the default).
On the Flux side:
[flux.security.auth.oidc]
enabled = true
issuer = "https://keycloak.example.com/realms/flux"
audience = "flux-server"
default_user_roles = ["viewer"]
Sanity-check the issuer by hitting https://keycloak.example.com/realms/flux/.well-known/openid-configuration — the issuer field in that response must match your config exactly (trailing slash and all). Mismatched issuers are the most common first-deployment 401.
Token lifecycle
OIDC tokens are short-lived. Flux validates every request from scratch — there is no server-side session, no refresh-token handling, and no token introspection callback. When a token expires, the next request returns 401; obtaining a fresh access token is the client’s responsibility (typically via the OAuth refresh-token flow against your IdP). JWKS keys are cached for jwks_cache_ttl seconds, so a key rotation at your IdP propagates within that window without restarting Flux.
What goes wrong first
- Audience mismatch. 401 with
OIDC token validation failed: Invalid audience. The token’sauddoes not equal the configuredaudience. Fix the IdP mapper or the Flux config — they must match exactly, including case. - Clock skew. 401 with
OIDC token expired. Either the token really is expired, or one of the two clocks has drifted. Run NTP on the Flux host and the IdP host; raiseclock_skewonly if you cannot fix the drift. - JWKS unreachable. Logged as
OIDC provider errorwith anhttpxconnection error. Flux cannot fetch the discovery document, so it cannot validate any token. Check egress ACLs from the Flux server to the IdP’s issuer hostname; the discovery URL is{issuer}/.well-known/openid-configurationand the JWKS URL is whatever that document advertises.