# 12 — Single sign-on: OpenID Connect and LDAP Owner, 2026-10-25: people can sign in to the web app (admin panel, user portal, browser softphone) with the company's identity provider as well as, or instead of, a SelfPhoneNG password: - **OpenID Connect** (S1–S3): "Sign in with Keycloak" on the login page. The owner's install uses Keycloak, but nothing here is Keycloak-only: any provider that follows OpenID Connect Core and Discovery works (§3.6 lists the ones checked). - **LDAP / LDAPS** (S4–S5): the normal password form, checked against the company directory (Active Directory, OpenLDAP, FreeIPA, lldap, Samba AD, 389 DS …) when the SelfPhoneNG password doesn't match. A user **connects** their OpenID account to their SelfPhoneNG account and can then sign in either way: with their password or with the provider. The admin picks which ways are on (§1.1). Only **web sign-in** changes. SIP passwords, voicemail PINs, desk phone provisioning, API tokens and the softphone's SIP login are untouched: a phone never talks to the identity provider. ## 1. Rules that hold for both - **Identities**: a user has a SelfPhoneNG password (or none), and at most one connected OpenID account and one directory account (`user_identities`, §2). Each is one more way to sign in to the same account; none replaces the others. - **Who someone is at the provider** is its stable id, never the email or name: OIDC `iss` + `sub`, LDAP the entry's UUID attribute (`entryUUID`, `objectGUID`, `ipaUniqueID`, `nsUniqueId`). - **No automatic accounts** (owner, 2026-10-25): signing in with a provider never creates a user. An admin creates every user on Users, as today; the provider only signs in users who exist. - **Break glass**: the last active admin with a password can't be deleted, disabled or demoted (extends `assertNotLastAdmin`), and admins can always sign in with a password, even in OpenID-only mode (§1.1). The identity provider being down never locks the owner out. - **One OIDC provider and one directory** per install. More than one OIDC provider: later (§7). - **Role**: when the provider's admin mapping is set (`adminGroups`, `adminFilter`), the role follows it on every sign-in and check through that provider (§3.4, §4.4), and Users shows it read-only (`PATCH role` → 409 `role_managed`). Unset: the role is managed in SelfPhoneNG as now. A sync never demotes or disables the last active admin with a password (it keeps them and logs a warning). - **Extensions** stay manual: an admin assigns one on Users. - **Our own session**: every sign-in makes the same `sp_session` cookie as today (04 §2.2, 14 days, sliding). `sessions.method` says how it was made. Provider tokens never reach the browser, except the ID token as `id_token_hint` in the sign-out redirect to the provider (§3.5; RP-initiated logout works that way). - **Two-step sign-in** (08 §2.4, owner 2026-10-25): password sign-ins ask for our TOTP code when it's on, whether the password was checked here or by the directory. OpenID sign-ins don't: MFA there is the provider's job. - **Self-signed or private CA** (a home-lab Keycloak or a domain controller): `NODE_EXTRA_CA_CERTS` (01 env list) names a PEM file mounted into the api container; Node then trusts it for both HTTPS and LDAPS. No "skip certificate check" switch anywhere. - The api runs with host networking (01), so a Keycloak or directory on the LAN is reachable as is. Both settings are admin-only, so the webhook private-address block (04 §9) doesn't apply. - **Never logged**: client secret, bind password, user passwords, authorization codes, ID/access/ refresh tokens. The OIDC callback route is `logLevel: 'silent'` (its query holds the code, like the caller-name route, 11 §6.1) and writes its own line without the query. ### 1.1 Sign-in mode (`auth.mode`) Settings → Sign-in, admin: | Mode | Login page | `POST /api/auth/login` (password, local or directory) | OpenID | |---|---|---|---| | `password` (default) | the password form | everyone | off (start/callback answer 404) | | `mixed` | the **Sign in with