# OIDC, LDAP and passkeys

> Set up OpenID Connect and LDAP sign-in for FeatherPanel, how passkeys, e-mail codes and QR login work, and how to turn off local password login.

Source: https://featherpanel.com/docs/api/sso/

FeatherPanel can sign users in with a local password, passkeys, a one-time e-mail code, Discord, any number of
OpenID Connect providers and any number of LDAP directories. Two-factor authentication (TOTP) sits on top of the
password login. Admins manage the providers in the admin area under **OIDC Providers** and **LDAP Providers**;
both need `admin.settings.edit`.

## OpenID Connect

Add a provider in **Admin > OIDC Providers**. The fields map to these columns:

| Field | Default | Meaning |
| --- | --- | --- |
| `name` | | Label on the login button. |
| `issuer_url` | | Issuer base URL. The panel reads `<issuer_url>/.well-known/openid-configuration`. |
| `client_id`, `client_secret` | | From your provider. The secret is stored encrypted. |
| `scopes` | `openid email profile` | Scopes sent in the authorization request. |
| `subject_claim` | `sub` | Claim that identifies the user. |
| `email_claim` | `email` | Claim that holds the e-mail address. |
| `group_claim`, `group_value` | empty | Optional gate: only users whose claim equals, or contains, this value may sign in. |
| `auto_provision` | `false` | Create an account on first sign-in. |
| `require_email_verified` | `false` | Refuse to link an existing account unless `email_verified` is true. |
| `enabled` | `true` | Show and accept this provider. |

Register this redirect URI with the provider:

```text
https://panel.example.com/api/user/auth/oidc/callback
```

### How a sign-in is matched

1. The panel sends the user to the provider with a random `state` and `nonce`, valid for 10 minutes.
2. On the callback it exchanges the code, verifies the ID token and rejects a nonce mismatch.
3. It looks the user up by provider and subject. If none is found and the provider says the e-mail is verified,
   it links the account with that e-mail.
4. If there is still no account, it creates one when `auto_provision` is on. The username comes from
   `preferred_username`, then `name`, then the e-mail; first and last names come from `given_name` and
   `family_name`. Otherwise the user is sent back with `oidc_user_not_found`.

Signed-in users can link or unlink a provider from their account page (`/api/user/auth/oidc/link` and
`/api/user/auth/oidc/unlink`). The login page lists enabled providers from the public
`/api/system/oidc/providers` route.

## LDAP and Active Directory

Add a directory in **Admin > LDAP Providers** and use **Test** to check the connection before you enable it.

| Field | Meaning |
| --- | --- |
| `host`, `port` | Directory server. |
| `use_ssl` | Connect with `ldaps://`. |
| `use_tls` | Run StartTLS on a plain connection (ignored when `use_ssl` is on). |
| `bind_dn`, `bind_password` | Service account. Leave empty for an anonymous bind. |
| `base_dn` | Where user searches start. |
| `user_filter` | Search filter with a `{username}` placeholder, for example `(sAMAccountName={username})` on AD. |
| `username_attribute`, `email_attribute`, `first_name_attribute`, `last_name_attribute` | Attribute mapping. |
| `required_group`, `group_attribute` | Only members of this group may sign in, read from `group_attribute` (default `memberOf`). Empty allows everyone. |
| `auto_provision` | Create the panel account on first LDAP sign-in. |
| `sync_attributes` | Update e-mail and names on every sign-in. |

Users sign in with `PUT /api/user/auth/ldap/login`, and can link an existing account to their directory entry from
the account page.

## Passkeys

Users add passkeys on their account page. The relying party ID is the host name of the panel's app URL, and only
that origin is accepted, so the app URL setting has to match the address in the browser. Passkey sign-in uses the
public `/api/user/auth/passkeys/authentication/options` and `/verify` routes; registration and management live
under `/api/user/passkeys`.

## E-mail codes, Discord and QR login

- **E-mail codes**: with `email_login_enabled` on and SMTP configured, users can ask for a 6-digit code that is
  valid for 10 minutes.
- **Discord**: set `discord_oauth_enabled`, `discord_oauth_client_id` and `discord_oauth_client_secret`. Users can
  sign in, register or link Discord to an existing account.
- **QR login**: with `auth_show_qr_login` on, the login page shows a QR code. A phone that is already signed in
  scans it and approves the desktop session. A QR challenge lasts 180 seconds.

## Login page options

| Setting | Effect |
| --- | --- |
| `login_methods_order` | Order of the methods: `local`, `passkey`, `ldap`, `email_code`, `discord`, `oidc`. |
| `login_hidden_methods` | Methods to hide from the page. |
| `login_default_method` | Method shown first. |
| `oidc_disable_local_login` | Block password and passkey sign-in for everyone without `admin.root`. |
| `require_two_fa_admins` | Require TOTP for admins. |

See the [settings reference](https://featherpanel.com/docs/panel/settings/) for every key.

## Related

- [API authentication](https://featherpanel.com/docs/api/authentication/)
- [Login, SSO and LDAP admin guide](https://featherpanel.com/docs/admin/authentication/)
- [Auth endpoints](https://featherpanel.com/docs/api/reference/user/auth/)

## Frequently asked questions

### What redirect URI do I give my OIDC provider?

Your panel URL followed by /api/user/auth/oidc/callback, for example https://panel.example.com/api/user/auth/oidc/callback. It is built from the app URL setting, so that setting must match the address users open.

### Can I force everyone to use SSO?

Set oidc_disable_local_login to true. Password and passkey sign-in then return LOCAL_LOGIN_DISABLED for everyone except users with admin.root, so you cannot lock yourself out.

### Why do passkeys stop working after I change the panel domain?

The WebAuthn relying party ID is the host name of the app URL. Passkeys registered for the old host do not work on a new one, so users have to add them again.
