# OpenID Connect

Sign in to the panel through an OpenID Connect provider.

## Set it up

1. Set the panel's public URL with `[server] url` or `KOGUT_URL`.
2. Create a confidential OpenID Connect application at your provider. Register this
   redirect URI for every provider:

   ```text
   https://kogut.acme.com/auth/oidc/callback
   ```

3. Add the provider and keep the client secret in the environment:

   ```toml title="kogut.toml"
   [[auth.oidc]]
   preset = "authentik"
   issuer = "https://auth.acme.com/application/o/kogut/"
   client_id = "kogut"
   client_secret = "${KOGUT_OIDC_SECRET}"
   admin_groups = ["kogut-admins"]
   ```

4. Set up the email and group claims below. With a provider that allows
   self-registration, restrict `allowed_domains` or turn self-registration off.
5. Open the panel and choose the provider's sign-in button. kogut reloads the file
   without a restart. To turn passwords off, see
   [Passwords](https://docs.kogut.dev/sign-in/passwords/#turn-passwords-off).

Before the first account exists, sign in as an `admin_groups` member or create the first
admin with the [setup token](https://docs.kogut.dev/sign-in/passwords/#the-first-account). Without admin groups, new
accounts are viewers; see [Roles](https://docs.kogut.dev/sign-in/roles/).

## Keys

All keys and defaults are in the [configuration reference](https://docs.kogut.dev/reference/configuration/#authoidc).
Repeat `[[auth.oidc]]` for more providers. Issuers need HTTPS except on localhost or
loopback. Discovery must return the configured issuer.

kogut checks the provider's TLS certificate against its bundled Mozilla roots, so
providers with private-CA certificates do not work.

## Presets

A preset sets the provider's scopes and groups claim. For other providers, omit `preset`
and set those values yourself.

| Preset | Issuer URL example |
|---|---|
| `authentik` | `https://<host>/application/o/<slug>/` |
| `authelia` | `https://<host>` |
| `keycloak` | `https://<host>/realms/<realm>` |
| `pocket_id` | `https://<host>` |
| `zitadel` | `https://<instance>.zitadel.cloud` |
| `kanidm` | `https://<host>/oauth2/openid/<client id>` |
| `google` | `https://accounts.google.com`, filled in automatically |
| `entra` | `https://login.microsoftonline.com/<tenant id>/v2.0` |

For Keycloak, add a group membership mapper to the client. For Authelia, kogut can read
email and groups from userinfo when the ID token omits them. The userinfo `sub` must match
the ID token.

### Authentik

If Authentik sends `email_verified: false`, kogut refuses sign-in. Only when admins
control email addresses and users cannot change them, create an `email` scope mapping
in **Customization → Property Mappings**:

```python
return {
	"email": request.user.email,
	"email_verified": True,
}
```

Select this mapping instead of the default email mapping under the provider's
**Advanced protocol settings → Scopes**.

### Pocket ID

Add a confidential OIDC client, not a public one, in Pocket ID with the redirect URI
above. Put admins in a user group listed in `admin_groups`:

```toml title="kogut.toml"
[[auth.oidc]]
preset = "pocket_id"
issuer = "https://id.acme.com"
client_id = "4f1c2b9e-7a3d-4c8e-9b21-5d6e7f8a9b0c"
client_secret = "${KOGUT_OIDC_SECRET}"
admin_groups = ["kogut-admins"]
```

If Pocket ID sends `email_verified: false` for a person, kogut refuses their sign-in.

### Zitadel

Turn on **Assert Roles on Authentication** in the project and use project role keys in
`admin_groups`. Turn on **User Roles Inside ID Token**, or kogut reads roles from
userinfo.

### Microsoft Entra

Use a tenant-specific issuer, not `common`, `organizations`, or `consumers`. Add the
optional ID token claims `email` and `xms_edov`; kogut requires `xms_edov: true`. Use
group object IDs in `admin_groups` unless your app sends group names.

### Google

Set `allowed_domains`. Google sends no groups, so do not set `admin_groups`:

```toml title="kogut.toml"
[[auth.oidc]]
preset = "google"
client_id = "1234567890-abc.apps.googleusercontent.com"
client_secret = "${GOOGLE_CLIENT_SECRET}"
allowed_domains = ["acme.com"]
```

## Link existing accounts

To choose a person's role before they sign in, add their email in **Settings → People**
with an empty password. Linking an existing account needs `email_verified: true`, or
`xms_edov: true` for Entra. Accounts with passwords can then use either method.

Later sign-ins match the issuer and `sub`, even if the email changes at the provider.
Another account at the same issuer cannot claim that kogut account. The panel cannot
detach it.

A new account can sign in when the provider omits `email_verified`, but an explicit
`false` blocks it. `allowed_domains` matches the email's domain exactly, not subdomains.
Keep email and group assignment under admin control at the provider.

## Fix sign-in failures

Read the error above the sign-in form and the server log entry `single sign-on failed`.

- Expired or missing sign-in cookie: finish within 10 minutes in the same browser, using
  the configured public URL.
- Provider connection failure: check the issuer, client secret, and redirect URI.
- Unverifiable token: check the server clock. kogut accepts RS256 and ES256 signatures,
  not unsigned or HMAC-signed ID tokens.
- Missing email: allow the `email` scope. For Entra, add the optional `email` claim.
- Unverified address: fix the provider's verification claim before linking an account.
- Incomplete setup: set the public server URL.

Signing out ends the kogut session, not the provider's session.
