Federated sign-in over OIDC, as a second door beside the password form — never a replacement for it.
Turning it on
Setting OIDC_ISSUER_URL is the on/off switch. Left empty, no discovery request is ever made and no
button renders on /login, though the SSO routes stay registered (GET /api/auth/oidc/login answers
404, and the callback redirects to /login?oidc_error=not_configured). Set it, and discovery is
fetched once and cached.
| Variable | Default | Notes |
|---|---|---|
OIDC_ISSUER_URL |
– | The provider’s issuer URL. On/off switch for the whole feature |
OIDC_CLIENT_ID / OIDC_CLIENT_SECRET |
– | Issued by the provider. OIDC_CLIENT_SECRET is a process secret like ADMIN_TOKEN — read from the environment, never written to the database, so no backup or dump ever carries it |
OIDC_REDIRECT_URI |
– | The callback URL registered with the provider, e.g. https://docs.example.com/api/auth/oidc/callback |
OIDC_SCOPES |
openid profile email |
Space-separated scopes requested at the provider |
OIDC_BUTTON_LABEL |
Single sign-on |
Text on the /login SSO button |
OIDC_AUTO_PROVISION |
0 |
1 lets a first successful provider sign-in create an account here on its own. Off by default: an unmapped provider identity is refused rather than silently handed an account |
OIDC_DEFAULT_ROLE |
member |
Role a provisioned account gets. admin or member only — never root |
There is nothing to configure from the dashboard for the provider itself; these variables are set by the operator in the instance’s environment. See Configuration for how settings are applied.
Signing in
A federated sign-in is always matched to an account by the provider’s sub claim, never its e-mail
claim — an e-mail is exactly what a misconfigured or compromised provider could forge into reaching a
different local account. Changing your e-mail at the provider does not disconnect you and cannot
accidentally connect you to someone else’s account.
Whether a new account can be created this way at all is the operator’s choice
(OIDC_AUTO_PROVISION), off by default. With it off, signing in through the provider is refused unless
an admin already created the account or you have linked it yourself (below). Turn it on and a first
successful sign-in mints an account with OIDC_DEFAULT_ROLE.
Once signed in, a federated account is governed by the same role table as any other account — nothing about what you can reach depends on how you signed in — and a provider outage never blocks local password sign-in. The two doors do not depend on each other.
Linking or unlinking your own account
Once signed in — by password or by the provider — every non-root account can connect or disconnect its own federated identity from its own account page, under Single sign-on. Connecting starts the identical provider redirect the login button uses, but links the identity it comes back with to the current session instead of signing in to whichever account already owns it. Linking a provider identity that is already linked to a different account is refused, not merged — each identity belongs to exactly one account.
Unlinking is self-service and revokes the account’s standing credentials in the same request: every
session and every API token that account holds is invalidated the moment the identity is removed —
including the session making the request — because unlinking is also what clears the way for a later
promotion to root, and nothing opened while the account was still provably tied to an external identity
provider should outlive that link. The same transaction revokes the account’s MCP OAuth credentials —
every access and refresh token it was issued for /mcp/<project> — so an MCP client signed in as that
account gets 401 on its next request and has to authorize again; other accounts’ credentials are
untouched. The audit event of the unlink records how many were revoked, as detail.revokedMcpCredentials.
Sign back in locally, since the link is gone, to get a working session again.
If the account has no local password and this link is its only way to sign in, unlinking is refused
(409 last_sign_in_method) — nothing removed, nothing revoked — until an admin sets a password for it
first with POST /api/users/:id/password.
root stays local, always
root cannot link a provider identity in the first place — the control never appears for it in the
dashboard, and the API refuses the attempt directly (403 root_local_only). The reverse direction is
guarded too: granting root to an account that still has a linked identity is refused
(409 root_requires_unlink), on the Users page and at the API, checked ahead of the write — so the
account’s role never actually becomes root while the link exists. As a second line of defence, a
session’s role and sign-in method are re-read on every request; if an account somehow did reach root
while still linked, a session opened over SSO for it would be signed out on its very next request rather
than allowed to act as root. Only after unlinking can another root account grant the root role.
This is why the one account this product cannot recreate from a provider claim — root — never signs in
over SSO.
The Admin API
Linking and unlinking are also two ordinary endpoints, callable from a script by a signed-in caller’s own session — see Admin API.