Single Sign-On
The Workbench can delegate interactive login to an OpenID Connect identity provider, so that people sign in with the account they already hold at the provider rather than with a Workbench password. This page describes what federated login changes, how to configure it, how to map provider groups onto Workbench groups, and the operational limits an administrator needs to know about before turning it on.
Federated login changes only how a person proves who they are. Authorisation is unchanged: a federated user reaches connections, MCP tools and administrative operations through exactly the same groups, privileges and token scopes as a local user, and the permission model applies to both in the same way.
Federated login is also a browser flow, so it does not help a headless MCP client. Service-account API tokens remain the mechanism for machine-to-machine access, as described in Token Management; by default a federated user cannot own API tokens at all, as described in API Tokens and Federated Users. Full OAuth 2.0 support for MCP clients is out of scope.
How a Federated Login Works
A federated login is an OpenID Connect authorisation code flow with PKCE, driven by two server endpoints.
The sequence is as follows:
- The login page shows a button whose label comes from
button_label, pointing at/api/v1/auth/oidc/start. - The server mints a short-lived login state, seals it into an
HttpOnlycookie that expires after ten minutes, and redirects the browser to the identity provider. - The person authenticates at the provider, which redirects the
browser back to
/api/v1/auth/oidc/callback. - The server exchanges the authorisation code for an ID token and, where the provider issues one, a refresh token; it verifies the ID token and reads the configured claims from it.
- The server matches the identity to a Workbench account, reconciles the account's mapped group memberships, and issues a session that expires when the ID token does, as described in Federated Sessions Follow the Provider.
The account is matched on the issuer and the sub claim together, held
in the users.external_subject column, and never on the username:
matching on the username would let an identity whose username claim is
admin take over the local admin account.
A failed login takes one of two shapes, and which one an operator sees
says where the failure happened. The callback answers 400 with a
generic JSON error when the request never reached the provider
successfully: no state cookie, a state cookie that does not open, a
state parameter that does not match the sealed one, a login state that
has already been accepted once, or neither an authorisation code nor a
provider error, since the provider's error
parameter is read first and takes the redirect branch below. It
redirects the browser back to the login page with a login_error
query parameter when the request did come back
from the provider: login_error=provider when the provider itself
declined, which includes the person cancelling at the consent screen,
and login_error=login when the code exchange or any later step
refused the login. Either way the real reason goes to the server log
only, and nothing derived from the provider's response reaches the
browser.
Configuring the Server
Federated login is configured in the http.auth section of
ai-dba-server.yaml, alongside the settings that govern local login.
The following example enables both login methods, provisions accounts
on first login, and maps two provider groups:
http:
trusted_proxies:
- "10.0.0.0/8"
auth:
local:
enabled: true
oidc:
enabled: true
issuer: "https://idp.example.com"
client_id: "pgedge-workbench"
client_secret_file: "/etc/pgedge/oidc-client-secret.txt"
redirect_url: "https://workbench.example.com/api/v1/auth/oidc/callback"
scopes: ["openid", "email", "profile", "groups"]
username_claim: "sub"
display_name_claim: "name"
groups_claim: "groups"
button_label: "Sign in with Example SSO"
provision_users: true
allowed_email_domains:
- "example.com"
superuser_group: "workbench-admins"
group_map:
workbench-dba: DBAs
workbench-readonly: Read Only
Sending SIGHUP re-reads the configuration file, and the server logs
each changed authentication setting as either applied or requiring a
restart. The login policy applies from the next login without a
restart: provision_users, allowed_email_domains, superuser_group,
group_map, button_label, auth_params, and setting enabled to
false, which
switches federated login off at once and refuses a login that is
already in progress. A reload therefore lets you stop just-in-time
provisioning, narrow the allowed email domains or remap a Workbench
group away from a compromised provider group whilst containing an
incident. The changes take effect at each person's next login, as
described in Revocation and Its Limits below, and a Workbench group
removed from group_map altogether stops being reconciled and keeps
its current members, so remap the group rather than removing it when
the aim is to revoke membership. Clearing superuser_group likewise
revokes nothing: federated users keep their current superuser status,
and no later login changes it, so point the setting at a group that no
one belongs to when the aim is to demote every federated superuser.
The identity provider decides the superuser flag, so a login is not
refused when removing the flag leaves no enabled superuser; the server
logs a warning instead. To restore access, add the account you need to
the superuser group at the provider, or add a local superuser with the
-add-user and -set-superuser command line options.
Switching federated login off stops new federated logins, and a
federated session already established ends when its ID token expires,
because the server no longer refreshes it. A session whose ID token
has not yet expired stays valid until then, and so do API tokens
already issued where allow_api_tokens is on, so follow the steps in
Revocation and Its Limits below to cut them off at once. When the server started with local.enabled set to
false, a reload that switches federated login off is refused and the
previous configuration is kept, because local login cannot be switched
back on without a restart and the server would otherwise be left with
no way to sign in.
The server logs a warning, as well as the note that the change applied,
when a reload widens access or looks like a revocation without being
one: setting enabled back to true, switching provision_users on,
emptying allowed_email_domains or adding a domain to it, setting,
changing or clearing superuser_group, mapping a provider group in
group_map to a Workbench group it was not mapped to before, or
removing a Workbench group from group_map, or switching
allow_api_tokens on.
The identity provider connection is built once, at start-up, from
issuer, client_id, client_secret, client_secret_file,
redirect_url, scopes and the three claim names, so a change to any
of those requires a restart. Setting enabled to true applies on
reload only when the server started with federated login switched on;
otherwise no provider was discovered and the server must be restarted.
local.enabled and max_failed_attempts_before_lockout also require a
restart.
The server compares the identity provider settings, and whether federated login was switched on, with the configuration it started with, so a change to one of them is reported as needing a restart on every reload until the server restarts or the setting is restored.
Local Login Settings
The http.auth.local section controls username and password login.
The following table describes the local authentication options:
| Option | Type | Default | Description |
|---|---|---|---|
local.enabled |
bool | true |
Enables username and password login. |
At least one login method must be available. The server refuses to
start when local.enabled is false and oidc.enabled is also
false.
Federated Login Settings
The http.auth.oidc section controls federated login. Every option
below is ignored while enabled is false, although the server still
refuses an auth_params entry that names a parameter it sets itself.
The following table describes the federated authentication options:
| Option | Type | Default | Description |
|---|---|---|---|
oidc.enabled |
bool | false |
Enables federated login. |
oidc.issuer |
string | Issuer URL of the identity provider; must use https, and discovery runs against it at start-up. Required. |
|
oidc.client_id |
string | OAuth 2.0 client identifier registered at the provider. Required. | |
oidc.client_secret |
string | Client secret supplied inline in the configuration file. | |
oidc.client_secret_file |
string | Path to a file holding the client secret, used when client_secret is empty. |
|
oidc.redirect_url |
string | Absolute URL the provider returns the browser to; its path must end with /api/v1/auth/oidc/callback. Required. |
|
oidc.scopes |
list | [openid, email, profile] |
Scopes requested at the provider; openid is added when absent, and so is offline_access unless the provider's discovery document lists its supported scopes without it. |
oidc.username_claim |
string | email |
Claim whose value becomes the Workbench username. |
oidc.display_name_claim |
string | name |
Claim whose value becomes the account's display name. |
oidc.groups_claim |
string | groups |
Claim carrying the user's provider group names. |
oidc.button_label |
string | Sign in with your identity provider |
Label of the federated login button. |
oidc.provision_users |
bool | false |
Creates a Workbench account the first time an unknown identity logs in. |
oidc.allowed_email_domains |
list | [] |
Restricts login to verified addresses in these exact domains; empty means no restriction. |
oidc.superuser_group |
string | Provider group whose members hold the Workbench superuser flag; empty leaves the flag under local control. | |
oidc.group_map |
map | {} |
Maps provider group names to Workbench group names. |
oidc.allow_unprefixed_state_cookie |
bool | false |
Accepts a login state cookie without the __Host- prefix behind a proxy; see Requiring a Trusted Proxy List. |
oidc.allow_api_tokens |
bool | false |
Lets accounts that sign in through the provider own API tokens; see API Tokens and Federated Users. |
oidc.auth_params |
map | {} |
Extra parameters appended to the authorisation request; see Adding Authorisation Request Parameters. |
Either client_secret or client_secret_file must be set. A secret
read from client_secret_file is held only in memory, so saving the
configuration cannot write it back to disk in plaintext; an inline
client_secret sits in the configuration file and should be protected
by file permissions accordingly.
The server refuses to start when oidc.enabled is true and issuer,
client_id, a client secret or redirect_url is missing, and it also
refuses to start when discovery against issuer fails. Failing at
start-up is deliberate: a provider that cannot be reached means nobody
can log in, which is better learned immediately than from a login page
whose button does not work.
Requiring a Trusted Proxy List
Set http.trusted_proxies to the reverse proxy's address ranges
whenever the Workbench sits behind a proxy, which in a supported
deployment it always does. The setting is required, not advisory, for a
production deployment behind a proxy; without it, any anonymous caller
can block federated login for every user, and the login state cookie
loses the protection that stops login CSRF from a sibling subdomain.
The start and callback endpoints are each rate limited per client
address, with an allowance of 240 requests a minute; a completed login
hands back the one unit it spent on each, so the limits bound failed
and abandoned logins rather than working ones. Every GET request to
the start endpoint spends one unit of the start allowance while
federated login is enabled; a request with another method, or one made
whilst federated login is disabled, is answered before the limit is
checked and costs nothing. The callback allowance is spent only by
requests that reach the identity provider, so a request refused
before that point costs nothing; such requests include one with no
login state cookie, one with a mismatched state and one that replays a
state already accepted.
Each login state is accepted by the callback once only. The server remembers a digest of each state the callback has accepted for as long as the state could still be opened, which is the ten minute lifetime of the state plus a minute of clock skew. A captured state cookie cannot therefore be replayed with further authorisation codes. The record holds up to 100,000 states at a time; under a sustained flood the oldest are forgotten early, and each forgotten state could then be presented once more. The record is held in the server's memory, so a restart forgets it and a state accepted before the restart could be presented once more until it expires; a deployment that runs several server processes with the same server secret refuses a replay only at the process that first accepted the state.
Behind a proxy, every request carries the proxy's address unless
http.trusted_proxies names it, so each limit collapses to a single
allowance shared by the whole deployment. An anonymous caller can then
spend that allowance with repeated requests to the start endpoint and
deny everyone else a login until the window passes; on a deployment
where local login is switched off, federated login is the only way in.
Setting http.trusted_proxies stops the allowance being shared across
every client, because each limit then applies to the caller's own
address.
The same list decides how far the server believes the
X-Forwarded-Proto header. Every cookie the server sets carries the
Secure attribute whenever the header says https, whichever address
the request came from, because a forged header can only cost the forger
their own cookie. The login state cookie additionally uses the
__Host- name prefix, which stops a sibling subdomain overwriting the
cookie, but only when the server terminates TLS itself or when the
header arrived from an address on http.trusted_proxies. Without the
list, a server behind a TLS-terminating proxy writes the cookie under
its plain name. A host that can set cookies for a sibling subdomain can
then plant the attacker's own login state over the user's cookie and
send the user's browser to the callback with the attacker's code; the
user ends up logged in to the attacker's account. The attack is login
CSRF (cross-site request forgery): the attacker gains no access to the
user's own account, but the user may enter data into an account the
attacker controls.
The server therefore refuses to start when federated login is enabled,
redirect_url uses https, http.tls.enabled is false and
http.trusted_proxies is empty, which is a TLS-terminating proxy with
no trusted proxy list. Set http.trusted_proxies to fix it, or, if
no sibling subdomain can be used to set cookies for the parent domain,
set oidc.allow_unprefixed_state_cookie to true to accept the risk.
A redirect_url using http on a loopback host is exempt, because a
browser never keeps a __Host- cookie over plain HTTP. A SIGHUP
reload applies the same rule and keeps the previous configuration when
it fails.
When the list is empty and federated login is enabled, the server
prints a warning at start-up about the shared rate-limit allowance;
when http.tls.enabled is also false, a second warning names the
login CSRF risk.
Registering the Workbench at the Provider
Register the Workbench as a confidential OAuth 2.0 client at the identity provider, with the authorisation code flow and PKCE enabled.
The provider needs the following from the Workbench:
- the redirect URI, which is the public URL of the Workbench with
/api/v1/auth/oidc/callbackappended, for examplehttps://workbench.example.com/api/v1/auth/oidc/callback. - the scopes the Workbench requests, which are
openid,emailandprofileby default, plus whatever scope the provider requires before it releases group membership, andoffline_access, which the Workbench adds to ask for a refresh token unless the provider's discovery document publishes ascopes_supportedlist that leaves it out. - permission to issue refresh tokens to the client, which some providers grant only when it is switched on in the client's registration; without a refresh token every federated session ends when its first ID token expires.
- a claim carrying group membership, if group mapping or a superuser group is to be used.
The provider issues a client identifier and a client secret in return,
which become client_id and client_secret_file.
The redirect URI registered at the provider and the redirect_url in
the configuration must be the same string, and both must be the URL the
browser actually reaches, not the server's internal address. Where TLS
terminates at a reverse proxy, that is the proxy's public URL; see
TLS and Reverse Proxy Requirements for
the headers the proxy has to forward.
The Workbench validates redirect_url only far enough to catch a typo.
It checks that the value is an absolute URL, that the scheme is https
(or http for a loopback host), that the path ends with the callback
path, and that there is no query string or fragment; a reverse proxy
prefix before the callback path is accepted. It does not and cannot
check the origin, so at start-up it prints the host the identity
provider will deliver authorisation codes to, which is where a typo
shows up. The control that stops an authorisation code being sent
somewhere else is the provider's own list of registered redirect URIs,
so keep that list tight.
Adding Authorisation Request Parameters
Some providers need a parameter in the authorisation request before
they behave as the Workbench expects. The auth_params option takes a
map of parameter names to values, and the Workbench appends each one
to the URL it sends the browser to when a login starts.
Google is the common case: it does not support the offline_access
scope, which its discovery document does not list, so the Workbench
does not request it. Google issues a refresh token only to a request
that carries access_type=offline, and only when the user grants
consent, which it skips after the first login unless the request
carries prompt=consent. Without both, a federated session ends when
its first ID token expires, typically after an hour. The following
example configures them:
http:
auth:
oidc:
enabled: true
issuer: "https://accounts.google.com"
client_id: "pgedge-workbench.apps.googleusercontent.com"
client_secret_file: "/etc/pgedge/oidc-client-secret.txt"
redirect_url: "https://workbench.example.com/api/v1/auth/oidc/callback"
auth_params:
access_type: "offline"
prompt: "consent"
With prompt: consent, Google shows its consent screen at every
login rather than only the first.
The Workbench sets some parameters itself, and refuses to start, or to
apply a reload, when auth_params names any of them: state,
nonce, code_challenge, code_challenge_method, redirect_uri,
client_id, response_type, scope, response_mode, request and
request_uri. The comparison ignores case. Request scopes through the
scopes option instead. Parameter names may contain only letters,
digits, _, - and .; a value may not contain control characters
or exceed 256 bytes, and the map may hold at most 16 entries. A reload
applies a change to auth_params from the next login.
Choosing the Username Claim
The value of username_claim becomes the Workbench username, and it
must satisfy the same username rule the -add-user command applies: the
first character must be a letter or a digit, the remaining characters
must be letters, digits, or one of _, ., @ and -, and the whole
value must be at most 128 characters.
A login whose username claim breaks that rule is refused, and the server logs a diagnostic naming the claim and the class of character that offended, so that the failure points at the configuration rather than looking like a bug.
The default of email is convenient but not universally safe. Logins
fail in both of the following common cases:
- a plus-addressed email such as
jane+work@example.comcontains a character the rule does not allow. - a Microsoft Entra ID guest account has a
preferred_usernameof the formjane_example.com#EXT#@tenant.onmicrosoft.com, which contains#.
Point username_claim at a claim whose values satisfy the rule. The
sub claim always does, and it is also the claim the account is matched
on, so it is the safest choice on any provider whose email addresses or
usernames are not strictly controlled.
Provisioning and Email Domains
Set provision_users to true to have the Workbench create an account
the first time an unknown identity logs in. The account is created with
an unusable password hash, so it can never be reached through password
login, and it starts with no groups and no superuser flag until
reconciliation grants them. The new account is recorded in the
audit log as user.create, attributed to the system
actor with a details.reason of identity_provider, the provider's
issuer in details.issuer and the identity's subject in
details.subject. If the event cannot be written, the account is not
created and the login fails.
Leave provision_users at false, which is the default, to pre-create
every account instead. A login by an identity that no account is linked
to is then refused, and an administrator links each account to its
provider identity with the server command line, as described in
Linking an Existing Account below.
The address itself always comes from the standard email claim, which
is not configurable, and the verification flag from the standard
email_verified claim.
The allowed_email_domains list restricts who may log in at all. An
empty list means no restriction. A non-empty list requires the identity
to carry an email address, requires the provider to have asserted
email_verified for the address, and requires the domain after the @
to match a listed entry exactly. Matching is case-insensitive and a
leading @ on a list entry is ignored, so @example.com and
example.com behave identically.
Domains match exactly and subdomains do not match: an address at
sub.example.com is refused by a list containing only example.com.
List every domain that should be allowed.
The address stored on the account in users.email is asserted by the
provider rather than proven. It is checked against email_verified
only when allowed_email_domains is non-empty; with no allow-list
configured, an unverified address is still stored on the account.
Treat the stored address as a label, never as a credential or as
grounds for granting anything.
Linking an Existing Account
An account is reached by a federated login only once the issuer and
subject of the provider identity are recorded against it. Automatic
provisioning records them when it creates the account; where
provision_users is false, an administrator records them with the
-link-oidc-user command.
Linking stamps the account exactly as provisioning would, so a linked account and a provisioned one are indistinguishable to login, to group reconciliation and to every later permission check.
The workflow starts with a failed login, because the server log is where the issuer and subject come from:
- Ask the person to attempt a federated login. It is refused, and the browser returns to the login page.
- Read the refusal from the server log. The line begins
[OIDC] Refusing federated login:and quotes both values, along with the command to run:
[OIDC] Refusing federated login: no account for federated identity
issuer "https://idp.example.com", subject "8a7f-sub" (username
"alice") and provisioning is disabled; link an existing account
with: ai-dba-server -link-oidc-user -username <name> -issuer
"https://idp.example.com" -subject "8a7f-sub"
- Link the pre-created account to that identity, passing the issuer and subject exactly as they were logged:
./bin/ai-dba-server -config /etc/pgedge/ai-dba-server.yaml \
-link-oidc-user -username alice \
-issuer "https://idp.example.com" -subject "8a7f-sub"
The command refuses an issuer that is not an https:// URL with a
host, since the server only accepts such an issuer in its own
configuration and no login could reach an account linked to
anything else.
- Ask the person to sign in again. The login now resolves to the linked account, and group reconciliation runs against it.
The command reports the account, the issuer, the subject and the stored
external subject key, and notes that password login is refused for the
account from that moment. Run -list-users afterwards to confirm the
link: the account's Authentication column now reads the issuer rather
than Local.
Linking Leaves Existing Credentials Working
Linking changes how the account is signed into. It does not revoke anything the account already holds, and two kinds of credential therefore survive it.
While allow_api_tokens is false, the default, every API token the
account owns is refused from the moment it is linked, including tokens
issued long before, because the server checks the owner's
authentication source each time a token is used; the tokens are refused
rather than deleted, and come back into use if the setting is switched
on. While allow_api_tokens is true, every API token the account
owns keeps working afterwards, at the account's full privilege, and
nothing about the link limits or expires it. An administrator linking
an account in order to tighten its authentication has then not
tightened anything until those tokens are dealt with: list them with
-list-tokens, and remove any that should not outlive the change with
-remove-token. This is exactly why the default unlink revokes them.
Any session the running server has already issued also keeps working, for the reason given in Sessions and the Command Line below.
Linking Hands Group Membership to the Provider
A linked account is deliberately indistinguishable from a provisioned one, so the next federated login reconciles its mapped groups exactly as it would for an account the provider created.
Linking a pre-existing local account therefore hands that account's
membership of every mapped group, and its superuser flag wherever
superuser_group is configured, to the identity provider from that
login onward. An administrator who links the local admin account
whilst superuser_group names a group the provider does not put them
in loses superuser at their next sign-in.
This is the reason the break-glass administrator account must stay unlinked, as described in Keeping a Local Break-Glass Administrator below.
Where any other existing account must be linked, confirm before linking that the provider asserts every group the account needs, including the superuser group.
Refusals and Relinking
Linking is refused rather than allowed to do something surprising in three cases.
The following table describes each refusal and the way past it:
| Situation | Outcome |
|---|---|
| The account is already linked to a different identity | Refused, naming the identity currently in the way; pass -relink to move the account. |
| Another account already holds the subject | Refused, naming the account that holds it; unlink that account first. |
| The account is a service account | Refused outright, because a service account can never hold a session and the link would never work. |
Linking an account to the identity it already holds succeeds and changes nothing, so a script that runs the command twice does not fail. A disabled account may be linked, which is convenient when accounts are prepared ahead of a rollout, though nothing works until the account is enabled.
To move an account to a new identity, for example after a provider
migration changes the subject, pass -relink:
./bin/ai-dba-server -config /etc/pgedge/ai-dba-server.yaml \
-link-oidc-user -username alice \
-issuer "https://idp.example.com" -subject "new-sub" -relink
Unlinking an Account
The -unlink-oidc-user command detaches an account from its provider
identity and returns it to local authentication:
./bin/ai-dba-server -config /etc/pgedge/ai-dba-server.yaml \
-unlink-oidc-user -username alice
The command has two branches, and which one runs decides what is left of the account's access.
The following table describes what each branch does to the three credentials an account can hold:
| Credential | Default | With -restore-password |
|---|---|---|
| Password | Replaced with an unusable hash | The password the account last held as a local account works again |
| API tokens | Revoked | Left in place |
| Browser sessions | Left running, see below | Left running, see below |
The default is the offboarding branch. It detaches the identity, so no
federated login is possible; it replaces the password hash with an
unusable one, so no password login is possible until an administrator
sets a password with -update-user -username <name>; and it revokes
every API token the account held, along with each token's scope rows.
All three take effect immediately. A session the running server has
already issued is a separate matter, covered below.
Pass -restore-password to convert the account back to local login,
for an administrator who means to hand it back to its holder:
./bin/ai-dba-server -config /etc/pgedge/ai-dba-server.yaml \
-unlink-oidc-user -username alice -restore-password
That branch is a conversion rather than a revocation, so it keeps the
account's API tokens as well as its password. Weigh it carefully. What
comes back is whatever hash the account last held as a local account:
the server refuses to write a password onto a federated account, so
nothing can have been set on it whilst it was linked, and the hash has
been inert for the whole federated period. The password it holds may
therefore be years old, will not have been rotated while the account was
federated, and may be known to the person who has just left; the tokens
have the same problem, and should be reviewed with -list-tokens and
removed with -remove-token where they are no longer wanted. An
account the provider originally created holds a hash of discarded
random bytes, so -restore-password on one of those
restores nothing usable.
Offboarding a Federated User
Removing a person's access takes two commands, in this order:
- Unlink the account without
-restore-password, which makes the password unusable and revokes the account's API tokens:
./bin/ai-dba-server -config /etc/pgedge/ai-dba-server.yaml \
-unlink-oidc-user -username alice
- Disable the account, which ends any session the running server still holds and stops new credentials being minted:
./bin/ai-dba-server -config /etc/pgedge/ai-dba-server.yaml \
-disable-user -username alice
The unlink alone is not enough, for the reason given in Sessions and the Command Line below. Revoking tokens removes the credentials that exist today; disabling the account refuses everything the account tries, including a session that was issued before either command ran.
The unlink command reports which branch ran, including how many tokens
it revoked, and the server log records the same. The account's
Authentication column reads Local again once the command has run,
which is the quickest confirmation that the identity is detached.
Sessions and the Command Line
Neither -link-oidc-user nor -unlink-oidc-user ends a session the
running server has already issued. Sessions live in the server
process's own memory, and these commands run in a separate process
against the account database, so a session that was minted before the
command runs keeps working until it expires. For a local session that
may be up to 24 hours later; a federated session ends when its ID token
expires, because the server will not refresh the session of an account
that is no longer federated. Both commands say so in their output.
Two commands do cut such a session, because the server re-reads the account's enabled flag from the database on every request:
-disable-user -username <name>disables the account, which refuses its sessions from the next request onward.- restarting the server discards every session it holds, for local and federated users alike.
This matters in two places. When offboarding, the unlink is not on its own a revocation; follow the sequence in Offboarding a Federated User above. When relinking an account to a new subject, whoever signed in under the previous identity keeps a working session until it expires, so disable the account or restart the server if that person must be cut off at once.
Linking never touches the account's API tokens either, as described in
Linking Leaves Existing Credentials Working above; unlinking revokes
them unless -restore-password is passed. Linking an account to the
identity it already holds changes nothing at all, which keeps a
configuration-management run that reasserts every link from disturbing
anybody on each pass.
Both commands record what they change in the audit log,
attributed to the cli actor named after the operating system user
who ran them: a link as user.link_identity and an unlink as
user.unlink_identity, each carrying the issuer and subject concerned
and the account's auth_source and external_subject before and after
the change. An unlink also records whether it made the password
unusable in details.password_changed, and each API token it revokes
as a token.delete event attributed to the same operator. A link or
unlink whose user.link_identity or user.unlink_identity event cannot
be written is rolled back and the command fails. Token revocation runs
after the unlink has been committed, so when a token.delete event
cannot be written the account stays unlinked, the command fails, and
every one of the account's API tokens remains valid; list them with
-list-tokens and remove them with -remove-token. A refused link or
unlink is recorded as a failure, and a link that changes nothing
records nothing.
Mapping Provider Groups to Workbench Groups
The group_map option maps a provider group name to a Workbench group
name. Nothing outside the map is consulted, so a provider can only
influence the Workbench groups an administrator has explicitly listed;
group names are never matched by equality across the two systems, and a
Workbench group is never created because the provider mentioned one.
Consider a provider that asserts the groups workbench-dba and
workbench-readonly, and a Workbench with existing groups named DBAs
and Read Only. The following configuration ties them together:
http:
auth:
oidc:
groups_claim: "groups"
superuser_group: "workbench-admins"
group_map:
workbench-dba: DBAs
workbench-readonly: Read Only
At each login, the Workbench compares the groups the provider asserted against the map and reconciles membership of the mapped groups only, so that:
- a user the provider puts in
workbench-dbais added toDBAs. - a user the provider no longer puts in
workbench-dbais removed fromDBAs. - a Workbench group the map does not name is left untouched in both directions, so a locally managed group is neither emptied nor joined.
A mapped Workbench group that does not exist is logged and skipped rather than failing the login, so a typo in the map cannot lock everybody out. Create the Workbench groups first, as described in Group Management, and check the server log after the first federated login.
Granting Superuser from a Provider Group
The superuser_group option names one provider group whose members
hold the Workbench superuser flag. The flag is granted while the
provider asserts that group and revoked as soon as it stops, and it is
never inferred from any other claim. Leaving superuser_group empty
leaves the flag entirely under local control, which is the right
setting wherever superuser should not be delegated to the provider's
group administrators.
The revocation is committed before any grant, so a reconciliation that fails part way through can only leave a user holding fewer privileges than the provider asserts, never more.
Every change reconciliation makes is recorded in the
audit log, attributed to the system actor with a
details.reason of identity_provider and the provider's issuer in
details.issuer: a superuser grant or revocation as user.update, and
a mapped group membership as group.member.add or
group.member.remove. A login that changes nothing records nothing. A
grant whose event cannot be written is rolled back and the login fails,
whereas a revocation stands even when its event cannot be written, and
the failure is reported in the server log.
Setting superuser_group hands out more than the flag's name suggests,
and the server prints a warning at start-up whenever it is configured. A
superuser bypasses every group grant, reaching every connection, every
MCP tool and every administrative permission from a browser session. An
API token the account owns is bound by the scope of that token, so a
scoped token stays within its scope, whereas an unscoped token carries
the same full privilege as the session. superuser_group changes who
can hand out that privilege, from a Workbench administrator to anyone
able to add a member to one provider group. Revocation is no faster than
any other mapped group: the flag is withdrawn at the person's next
login, and until then a browser session they already hold keeps full
privilege, as does an unscoped API token they own, whilst a scoped token
keeps whatever its scope allows; disable the account to revoke any of
them sooner. Leave the option empty unless the provider's group
administrators are trusted to that extent.
Keeping Mapped Groups Flat
Do not nest a mapped group above other Workbench groups. A mapped group must have no member groups of its own.
Membership is resolved by walking upwards from a user's direct memberships, so a user who is a member of a group nested inside a mapped group holds the mapped group by inheritance. Reconciliation writes only the direct membership row, so removing the direct row leaves the inherited path intact, and the user keeps everything the mapped group confers after the provider has stopped asserting it. Keep every mapped group at the bottom of the hierarchy, and grant nested structure through groups the map does not name.
Federated Sessions Follow the Provider
A federated session lasts only as long as the identity provider keeps vouching for the person. It expires when the ID token from the login expires, which for most providers is an hour or less, rather than after the 24 hours a local session gets. Refreshes can extend it, but never beyond 24 hours after the login, at which point the session ends and the person signs in again, as they would with a local session.
The first request after that expiry asks the provider to redeem the refresh token it issued at login. The provider refuses once the person has been removed or disabled there, or the grant has been revoked; the session then ends, the request is refused as unauthenticated, and the server log records why. When the provider agrees, the session runs on to the new ID token's expiry, and where the provider returns a new ID token the server checks it as a login would: the token must name the subject the account is linked to, and the email domain policy applies.
The account's mapped groups and superuser flag are reconciled from a refreshed ID token only when that token carries the configured groups claim. Many providers leave the claim out of a refreshed ID token, and some return no ID token on refresh at all; in either case the memberships and superuser flag set at the last login stay as they were. A refreshed ID token that carries the claim is authoritative, so an empty list revokes the account's mapped groups and superuser flag in the same way an empty claim at login would. With a provider that never repeats the claim on refresh, a change to someone's groups at the provider takes effect at their next login, which is at most 24 hours after the last one because that is as long as a federated session can run; removing or disabling them there still ends the session at the next refresh.
The server also ends a federated session without asking the provider when federated login has been switched off, or the account has been disabled, deleted or unlinked. A provider that issues no refresh token gives sessions that end at the first ID token's expiry, after which the person signs in again; a session left idle for more than 24 hours after its expiry is discarded and needs a fresh login.
The refresh token is held in the server's memory with the session, and is never written to disk or to the log. Only one refresh runs for a session at a time, so a browser with several tabs open does not present the same refresh token twice; a refresh that takes longer than 15 seconds fails and ends the session.
Any refresh that fails ends the session, whatever the reason, because the server cannot tell an unreachable provider from one that has stopped vouching for the person. During a provider outage, every federated user is therefore signed out within one ID token lifetime, typically an hour, and cannot sign back in until the provider recovers. A local superuser is the way in for that period, as described in Keeping a Local Break-Glass Administrator.
API Tokens and Federated Users
A federated user's API token is not tied to a session, so it never passes the refresh check above: the provider is never consulted when a token is used. A token held by a person who has been removed at the provider would otherwise keep working until it expired.
The allow_api_tokens setting therefore defaults to false. While it
is off, the following applies to every account that signs in through
the identity provider:
- creating a token for the account is refused with
403 Forbiddenthrough the REST API, and refused by-add-tokenat the command line, even for a superuser, and the refusal is recorded in the audit log as a deniedtoken.createevent. - any token the account already owns is refused on use, including a
token issued before the account was linked; the caller sees the same
401 Unauthorizedas for any invalid token, and the audit log records a deniedtoken.useevent naming the token. GET /api/v1/user/inforeportsapi_tokens_allowedasfalsefor the account, so that the console can explain that API tokens are disabled for federated logins in this installation; the console's Tokens panel reads the setting again each time it opens. When the server cannot read the account, it leaves the field out rather than reportfalse.
Local accounts and service accounts are unaffected, and automation should use a local service account, as described in Token Management.
Warning
With allow_api_tokens set to true, a federated user's API
tokens survive that person's removal at the identity provider, and
keep working until each token expires or is revoked. Pair the
setting with a short http.auth.max_user_token_days, and make
removing the account's tokens part of offboarding, as described in
Offboarding a Federated User.
The setting applies on reload, and the server logs a warning when a
reload switches it on. Switching it back off refuses the existing
tokens from the next request onward without deleting them. The
command line reads the setting from the configuration file named with
-config.
Revocation and Its Limits
Reconciliation runs at login, and when a federated session is refreshed with an ID token that carries the groups claim, as described above. Nothing re-evaluates a provider's assertions in the background, so the Workbench cannot notice a change made at the provider until the person's session next needs refreshing or the person next logs in.
A user removed from a mapped group at the provider therefore keeps
whatever that group confers until their session's ID token next
expires, or, where the provider leaves the groups claim out of
refreshed ID tokens, until their next login, which is at most 24 hours
after the last. API tokens never trigger a refresh, so where
allow_api_tokens is on, the holder of a long-lived token keeps that
group's privileges until the token expires. The same applies to the
superuser flag when it comes from superuser_group.
Disabling or deleting the Workbench account is the only complete revocation. Disabling an account refuses both its logins and its API tokens immediately, and reconciliation refuses to touch a disabled account, so an administrator containing an incident cannot have the privileges quietly reinstated underneath them.
Keeping a Local Break-Glass Administrator
Keep at least one local superuser account with a password, keep
local.enabled set to true, and never link that account to the
identity provider.
The identity provider is a dependency of every federated login, so an expired client secret, a rotated signing key, a failed discovery endpoint or an outright provider outage locks out every federated user at once. Discovery failure is fatal at start-up, so a server restarted during a provider outage does not come up at all with federated login enabled. A local administrator who can still sign in is the difference between editing the configuration calmly and rebuilding access under pressure.
Linking that account would make it depend on the two things it exists
to be independent of. A linked account can be reached only through the
provider, so a provider outage takes it out with everything else, and
its superuser flag is reconciled at each login from
superuser_group, so it holds superuser only while the provider keeps
asserting that group. Leave the account local, let the provider grant
superuser to separate federated accounts, and the recovery path
survives whatever has happened to the provider.
Test the local account periodically, and store its password where it
can be reached without the Workbench. Switching local.enabled to
false is reasonable once federated login is proven, but it removes
this recovery path.
Sessions Do Not Survive a Restart
Sessions are held in memory, so every session ends when the server restarts and everyone signs in again. This applies to local and federated logins alike, and it is pre-existing behaviour rather than something federated login introduced.
A local session lasts 24 hours, and a federated session lasts as long as its ID token, extended by refreshes for at most 24 hours after the login, as described in Federated Sessions Follow the Provider. Each user may hold at most ten simultaneous sessions; an eleventh login evicts that user's oldest session.
Identifying Federated Accounts
An account either holds a password in the Workbench or signs in through
the identity provider, and the console, the server command line and the
REST API each report which. None of the three reports the provider
subject, which is a long opaque identifier that tells an administrator
nothing, so only the issuer is shown; the subject appears only in the
output of -link-oidc-user, which echoes back the value the operator
has just supplied on the command line.
In the Console
The Users page of the Administration console marks a federated
account in its Type column.
Such an account carries a Federated chip, with the issuer on one line
beneath the chip; a long issuer is shortened to fit the column and the
full value appears in a tooltip. A local account shows its usual type,
exactly as before.
Opening a federated account in the Edit user dialog replaces the
password field with a notice explaining why the field is absent. The
notice names the provider the account signs in through, says that the
account has to be unlinked before it can sign in locally, and warns that
the account's group membership, along with its superuser flag wherever
superuser_group grants one, is reconciled at every sign-in. The
Superuser toggle stays available, because an administrator may still
grant the flag, subject to the reconciliation described below.
At the Command Line
The -list-users command reports the same thing in its Authentication
column, which is the quickest way to audit every account at once.
The following listing shows two federated accounts and four local ones:
./bin/ai-dba-server -list-users
Auth store: /var/lib/ai-workbench/data/auth.db
Users:
=================================================================================================================
Username Created Last Login Status Notes Authentication
-----------------------------------------------------------------------------------------------------------------
Alice 2026-06-10 13:24 Never Enabled Developer https://idp.example.com
Bob 2026-06-10 13:31 Never DISABLED developer Local
Carol 2026-06-10 13:32 Never Enabled Management https://idp.example.com
Dan 2026-06-10 13:37 Never Enabled sales Local
admin 2026-06-09 11:59 2026-06-10 12:27 Enabled management Local
inventory 2026-06-10 13:31 Never Enabled Software Local
=================================================================================================================
The column reads Local for an account that authenticates here, and the
issuer for an account the provider owns. The column comes last and is
printed in full, so two issuers that differ only near the end, such as
two tenants or realms of the same provider, remain distinguishable.
The column reads OIDC in the rare case of an account marked as
federated whose stored identity cannot be parsed, which is worth
investigating because a login can no longer match such an account.
Through the REST API
The user objects returned by GET /api/v1/rbac/users and by
GET /api/v1/rbac/users/{id}/privileges carry the same information in
two fields.
The following table describes the two fields:
| Field | Description |
|---|---|
auth_source |
Always present; reads local for an account that authenticates here and oidc for one the provider owns. |
auth_issuer |
Present only for a federated account; holds the issuer URL of the identity provider. |
An account stored before the users.auth_source column existed reports
as local, so the field is safe to branch on without a fallback.
Why a Superuser Flag Keeps Reverting
A superuser flag that an administrator grants and that is gone again
shortly afterwards belongs to a federated account whilst
superuser_group is configured.
The flag is reconciled at every federated login from the provider group
named by superuser_group, as described in Granting Superuser from a
Provider Group above, so a grant made in the console or with
-update-user survives only until the person next signs in. Check the
Authentication column or the Federated chip first: where the account
is federated, grant the flag by adding the person to the provider group,
and where the account is local, the flag stays as it was set.
Who Still Signs In Locally
Every account whose authentication reads Local depends on
http.auth.local.enabled, so review that list before setting the option
to false.
Turning local login off locks out every such account, including the
break-glass administrator described below, and the setting takes effect
only at a restart, so the mistake is discovered at the worst moment.
Note that a service account also reports as Local although the account
never signs in interactively; a service account reaches the Workbench
with an API token, which federated login does not change.
Federated Accounts in the Console
Federated accounts appear on the Account Management page alongside local ones, and are administered there in the same way: they can be disabled, enabled, deleted, added to groups and given API tokens.
One difference matters. A federated account cannot be given a working
password, because password authentication is refused for any account
whose identity the provider owns, so the Edit user dialog offers no
password field for one. Unlink the account, as described in Unlinking
an Account above, to return the account to local login.
Troubleshooting
Federated login failures are deliberately opaque in the browser; the server log carries the real reason. Check the log first.
Every Login Is Refused Immediately
A username claim that breaks the Workbench username rule refuses the
login before any account is touched, and logs a diagnostic naming the
claim. Set username_claim to sub, or to another claim whose values
satisfy the rule.
A non-empty allowed_email_domains list also refuses a login when the
provider sends no email address, sends one the Workbench cannot use, or
has not set email_verified. Check that the requested scopes include
whatever the provider requires before it releases a verified address.
A Pre-Created Account Is Not Recognised
A refusal logged as no account for federated identity means no
account carries that issuer and subject. With provision_users set to
false this is the expected first attempt: link the account as
described in Linking an Existing Account. With provisioning enabled it
means the account could not be created, usually because the asserted
username is already taken by another account, which is also logged.
The Login Button Does Nothing
A button that returns to the login page means federated login is not
active on the server. Confirm that oidc.enabled is true and that
start-up reported OIDC login: ENABLED rather than a discovery
failure.
The Provider Rejects the Redirect
A provider that refuses the redirect URI, usually with an error on its
own error page, has a registered URI that differs from redirect_url.
Compare the two character for character, including the scheme, the
host, any port and any reverse proxy path prefix.
Groups Are Not Applied
A group that is asserted but not applied is usually a name that
group_map does not list, or a mapped Workbench group that does not
exist; both are logged. A groups claim of an unexpected shape, or with
values the Workbench refused, is logged too, and the login proceeds
with fewer groups than the provider sent.
A group that was applied and is not being removed is usually the nesting problem described in Keeping Mapped Groups Flat.