---
title: "Configure an identity provider"
description: "Register the callback URL with the provider, then add its issuer and client to the cluster."
canonical_url: "https://docs.oz0.ai/admin/sign-in/identity-providers"
---
# Configure an identity provider

> Register the callback URL with the provider, then add its issuer and client to the cluster.

For: cluster and tenant administrators.

Register the callback URL with the provider, then add its issuer and client to the cluster.

## Add an identity provider

Register Orchestrator Zero with the provider as a web application that uses the authorization code flow, with this callback URL (also called the redirect URI):

```text
https://<the address people use for the web UI>/api/auth/oidc/<id>/callback
```

`<id>` is a short name you choose, such as `okta`. Then give the provider's issuer URL and client ID to the cluster:

```bash [Terminal]
printf %s "$OKTA_CLIENT_SECRET" | oz0 oidc set okta --display-name Okta \
  --issuer https://acme.okta.com --client-id 0oa1b2c3d4 --client-secret-stdin \
  --username-claim email --scope openid --scope profile --scope email --scope groups
oz0 oidc list      # each provider, its rules, and the callback URL to register
```

The sign-in page now has a Sign in with Okta button. Cluster admins can do the same under *Admin*, *Access*, *Sign-in methods*.

- The client secret is sealed with the cluster's master key and never shown again. Leave it out for a public client: the code is then protected by PKCE alone, which providers such as Keycloak and Entra ID allow.
- The issuer must use `https`. The server reads its discovery document when you save it and warns when it gets no answer; the provider is saved either way.
- Behind a load balancer, start the servers with `--public-url https://oz0.acme.se`, so the callback URL is the one people use and the CLI prints it in full.
- `oidc set` changes only the flags you give, so `oz0 oidc set okta --disabled` turns a provider off and `--disabled=false` turns it back on. Turning it off also signs out everyone who signed in through it.
- `oz0 oidc remove okta` removes a provider. Everyone signed in through it is signed out, and its links to accounts and the tenant roles its group rules gave go with it. The accounts stay, and so do cluster roles its rules set; an account with no password and no other provider can no longer sign in.

### What the server checks

Each sign-in uses the authorization code flow with PKCE (S256), a `state` and a `nonce`. The server checks the ID token's signature against the provider's published keys (JWKS), its issuer, that this client is in its audience (and its authorized party when it has several), its expiry and the nonce. Anything else fails the sign-in. The page then says only that the provider's answer did not check out; the server's log says why.

### Accounts

A person's account is named by their username claim: `preferred_username` unless you set `--username-claim`, such as `email`. Account names are lowercase; other characters become dashes. A claim longer than an account name (63 characters) makes no account and the page says so: it is never cut short, since two long names that differ only past the cut would name one account.

- The first sign-in links the person (the provider's subject) to an account, and later sign-ins use that link, whatever the claim says by then.
- An email address must be verified to name an account. Most providers let people put any address in their profile, and with `--username-claim email` the address decides which account a first sign-in creates or links, with the roles that account has. So the server takes the address only when the ID token says `email_verified: true`. A token that says `false`, or says nothing, creates and links nothing, and the page tells the person why; someone already linked signs in as before.<br />

For a provider that verifies or sets every address itself and sends no such claim, a cluster admin can take its addresses as verified with `--assume-email-verified`: Microsoft Entra ID is one ([in this guide](https://docs.oz0.ai/admin/sign-in/identity-providers#microsoft-entra-id)). A token that says `false` is refused either way. Other username claims do not depend on `email_verified`.
- With `--create-users`, someone without an account gets one at their first sign-in, as a `member` of the cluster (or `--default-role reader`).
- Without it, a cluster admin adds the account first, without a password: `oz0 user add kim@acme.se --no-password --provider okta`. The first sign-in through `okta` of a person whose username is exactly `kim@acme.se` links it, once. Leave out `--provider` when the cluster has one identity provider: the link names that one. A link always names its provider; with several providers, or none yet, the command asks for `--provider`.
- Only the exact name links a waiting account. The name is made from the claim by lowering its case and replacing characters, so `Kim`, `kim.` or ` kim` would all make `kim`; none of them links the waiting account `kim`, which keeps waiting for a person whose claim is `kim` itself.
- A name alone never reaches an account that exists. Providers do not promise that usernames are unique or kept, and two providers can have the same names. So a person whose username names an account that is already linked, was created by a provider, or has a password, is refused, and the page says so. Only an account a cluster admin marked as waiting (`user add --no-password` or `user link`) is linked by name, and only once.
- To move an existing account to a provider, remove its password if it has one, then let the next sign-in link it:```bash [Terminal]
oz0 user password kim --remove
oz0 user link kim --provider okta
oz0 user unlink kim --provider google   # remove an old link, which also signs kim out
```

<br />

Cluster admins can do the same in the person's dialog under *Access*. `oz0 user list` shows `waiting:okta` in the SIGN-IN column until the link is made. An account made ready before links named their provider shows `waiting:any`, and links nobody: run `oz0 user link NAME --provider ID` for it again.

### Roles from groups

Group rules turn the provider's groups into roles, at every sign-in:

```bash [Terminal]
oz0 oidc set okta --groups-claim groups \
  --cluster-role oz0-admins=admin \
  --group-role eng=default:operator \
  --group-role contractors=acme:operator:no-content \
  --group-role auditors=acme:auditor \
  --group-role chat-users=acme:agent-user:agents=helpdesk,tag:hr
```

- `--cluster-role GROUP=ROLE` gives members of the group a cluster role: `admin`, `reader` or `member`. Once a provider has such a rule, it decides the cluster role of the accounts that sign in with it: someone who leaves the group is a `member` (or the default role) at their next sign-in.
- `--group-role GROUP=TENANT:ROLE` gives a [role in a tenant](https://docs.oz0.ai/admin/tenants#roles-and-permissions) (any role in force: the built-in ones, such as `auditor` or `agent-user`, and a role set's), with `:content` or `:no-content` to override whether the role sees what the tenant's jobs read and wrote, and `:agents=A,B` last for an [agent grant](https://docs.oz0.ai/admin/tenants#roles-and-permissions) (agents, `<plugin>/*` or `tag:<tag>`).<br />

When several rules match a tenant, the person gets the union of their roles' permissions, the content permission if any rule gives it, and every rule's agent grant, whatever the rules' order.<br />

Groups that would make someone an auditor and also give a role that changes the tenant keep `auditor` alone, unless the tenant allows both ([separation of duties](https://docs.oz0.ai/admin/tenants#roles-and-permissions)); the audit log says so when the membership changes, and `oz0 doctor` lists them (`roles.sod_kept`). An `agent-user` rule sees content by default (the person's own chats and jobs only).
- A role the provider gave is replaced or removed at the next sign-in, when the groups change. A role an owner set by hand stays as the owner set it, so you can give one person more than their group.
- Rules apply when someone signs in. Someone removed from a group keeps their role until their next sign-in or until their session ends; sign them out under *Access*, *Sessions*, or with `oz0 user signout NAME`.

Ask the provider to put groups in the ID token: in Okta, a `groups` claim on the authorization server; in Entra ID, *Token configuration*, *Add groups claim* (Entra sends group IDs unless you choose names); in Keycloak, a *Group Membership* mapper with *Full group path* off.

### Microsoft Entra ID

Register an application in Entra ID (*App registrations*, *New registration*) with a *Web* redirect URI, the callback URL above, and give the cluster your directory's issuer, which has the directory (tenant) ID in it:

```bash [Terminal]
printf %s "$ENTRA_CLIENT_SECRET" | oz0 oidc set entra --display-name Microsoft \
  --issuer https://login.microsoftonline.com/11112222-bbbb-3333-cccc-4444dddd5555/v2.0 \
  --client-id 66667777-eeee-8888-ffff-9999aaaabbbb --client-secret-stdin \
  --username-claim email --assume-email-verified
```

Entra ID's ID tokens carry no `email_verified` claim. Their `email` is the address your directory has for the person, which the directory sets (its admins, its sync from an on-premises directory, or whoever invited a guest), not the person. So with `--username-claim email`, sign-in through Entra creates and links no account until you take its addresses as verified: `--assume-email-verified`, *Take its addresses as verified when its tokens do not say* under *Scopes and claims* in the provider's dialog, or `assume_email_verified: true` in a file.

Since the issuer is your directory's own, tokens from other Entra directories do not check out. Turn the setting on only for a provider like this one: for a provider that lets people set their own address and sends no claim, anyone who put a colleague's address in their profile could sign in as the colleague.

### From a file

A server can also read its providers from a YAML file at start, which suits configuration management. It saves what differs from what the cluster has, as `system:config` in the audit log:

```yaml [oidc.yaml]
providers:
  - id: okta
    display_name: Okta
    issuer: https://acme.okta.com
    client_id: 0oa1b2c3d4
    client_secret_file: /etc/orchestrator-zero/okta-secret   # or client_secret_env: OKTA_CLIENT_SECRET
    scopes: [openid, profile, email, groups]
    username_claim: email
    create_users: true
    group_roles:
      - group: oz0-admins
        cluster_role: admin
      - group: eng
        tenant: default
        role: operator
```

```bash [Terminal]
oz0 server start --oidc-config /etc/orchestrator-zero/oidc.yaml --public-url https://oz0.acme.se
```


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
