Identity providers
This page is for administrators who want a "Sign in with…" button on the login page. The gateway supports OpenID Connect (OIDC) with the authorization code flow and PKCE. Local email and password accounts keep working beside it.
What it does
A provider is an outside service the gateway trusts to say who someone is. When it is switched on, the login page shows a button with the provider's name. Clicking it sends the browser to the provider; the provider sends it back with a one-time code; the gateway trades the code for a signed identity token, checks it, and signs the person in with a normal gateway session.
Nothing the browser carries back is trusted until the provider's signed token has been checked.
The Sign-in providers page
Open Gateway, then Security, then Sign-in providers. The page lists the providers; selecting one opens a panel beside the list. Only administrators can open it.
Adding a provider
- Register an application at your provider and note its client id and, if it issues one, its client secret.
- Click New provider.
- Under Provider, enter the Name on the button (up to 60 characters) and the Issuer address. This is the address the provider publishes its settings under; the gateway reads
<issuer>/.well-known/openid-configurationfrom it. It must behttps://. Plainhttp://is accepted only forlocalhostor a loopback address, for local testing. - Under Credentials, enter the Client id (up to 300 characters) and, optionally, the Client secret. On an edit, a blank secret keeps the one already saved.
- Under Who may sign in, choose the Role at first sign-in and, optionally, the Email domains that may sign in this way. Switch Link accounts on only if you want a provider sign-in to take over an existing local account with the same email (see below).
- Under State, leave On switched on so the button shows on the sign-in page.
- Click Add provider.
- The panel now shows the Redirect address. Register it with the provider exactly as written. It is
<AUTH_BASE_URL>/api/auth/idp/<provider id>/callback, soAUTH_BASE_URLin the gateway configuration must be the public address of the gateway. - Click the button under Check the connection. The gateway reads the provider's discovery document and reports the authorization endpoint it found, or the error. The button is disabled while the form has unsaved changes.
Scopes default to openid email profile. The API accepts a scopes field; openid is always added if missing, because without it there is no identity token to check.
Switching a provider off, or deleting it
Switch On off to hide the button and refuse sign-ins through that provider while keeping its settings. Delete provider removes the provider and the links it made between provider identities and gateway accounts. The people themselves stay, with their role, and can still sign in with a password if they have one.
What is checked at sign-in
- Start.
GET /api/auth/idp/{id}/startreads the provider's discovery document, makes a freshstate,nonceand PKCE verifier, stores them in an HttpOnly cookiescadex.idpscoped to/api/auth/idpthat lives 10 minutes, and redirects to the provider's authorization endpoint withresponse_type=code,code_challenge_method=S256, the scopes, thestateand thenonce. - Callback.
GET /api/auth/idp/{id}/callback?code&statespends the cookie, whatever happens next (one try per start). Thestatemust match the cookie, the cookie must not have expired, and the provider must still be enabled. - Token exchange. The code goes to the provider's token endpoint with the PKCE verifier and, if one is saved, the client secret. The answer must carry an
id_token. - Token validation. The identity token must be signed with one of the provider's published keys (
jwks_uri) using RS256/384/512, ES256/384/512 or PS256/384/512;noneand shared-secret algorithms are refused. Its issuer must equal the configured issuer, its audience the client id, it must not have expired (2 minutes of clock skew allowed), and itsnoncemust equal the one this browser started with. It must name a subject (sub) and an email with an@. The name is taken fromname, thenpreferred_username, then the part of the email before@. - Account resolution. See the next section.
The discovery document itself is checked too: its issuer must equal the configured one, and its authorization, token and JWKS endpoints must be https:// (or local http://). Outbound requests to the provider time out after 10 seconds.
Which account a sign-in becomes
| Situation | Result |
|---|---|
The provider's subject (sub) was linked to a gateway account before | That account signs in. The email is not consulted |
New subject, and the token does not say email_verified: true | Refused: email_not_verified |
| New subject, and Email domains are set but the email's domain is not among them | Refused: domain_not_allowed |
| New subject, email matches an existing account, Link accounts off | Refused: account_exists |
| New subject, email matches an existing administrator | Refused: account_exists, whatever Link accounts says |
| New subject, email matches another existing account, Link accounts on | The subject is linked to that account, which signs in with its current role |
| New subject, no account with that email | A new account is created with the provider's Role at first sign-in |
The first-sign-in role can be viewer, operator or engineer, never admin; the API refuses anything else and the value is checked again at sign-in, so a hand-edited row cannot make a sign-in an administrator. A provider sign-in never changes an existing account's role.
Email domains are compared case-insensitively; a leading @ is stripped on save. Up to 20 domains, each a plain name such as example.com.
When a sign-in fails
The browser is sent to /login?error=<code> with one of: provider_unavailable, provider_denied, invalid_state, token_exchange_failed, token_invalid, no_email, email_not_verified, domain_not_allowed, account_exists, sign_in_failed. Nothing the provider said is passed to the browser; the gateway log holds a warning with the provider's name and the reason.
Secrets
The client secret is encrypted with ASP.NET Data Protection before it is stored and is never returned by the API, which only reports hasSecret. The keys live in DATA_DIR/keys; if the database is restored under other keys the secret cannot be read, the sign-in fails with provider_unavailable, and the gateway logs an error saying so. Enter the secret again in that case. See Backup and restore.
Limits
- OpenID Connect only. No SAML, no LDAP.
- No group or claim-to-role mapping: everyone who signs in through a provider for the first time gets the same role.
- No single sign-out: signing out of the gateway does not sign the person out of the provider, and the provider cannot end gateway sessions.
- The flow has been tested against an in-process mock provider in the test suite, not yet against a real Entra ID, Google, Okta or Keycloak tenant.
API
Administration routes need the admin role; the sign-in routes are anonymous. The full reference is in Admin API and Auth API.
| Route | Purpose |
|---|---|
GET /api/v1/admin/identity-providers | List providers, each with redirectUri and hasSecret |
POST /api/v1/admin/identity-providers | Create: {name, issuer, clientId, clientSecret?, scopes?, defaultRole?, allowedDomains?, linkExistingAccounts?, enabled?} |
PUT /api/v1/admin/identity-providers/{id} | Replace the same fields; a blank clientSecret keeps the saved one |
DELETE /api/v1/admin/identity-providers/{id} | Remove the provider and its account links |
POST /api/v1/admin/identity-providers/{id}/test | Read the discovery document: {ok, authorizationEndpoint} or {ok: false, error} |
GET /api/auth/idp | Enabled providers for the login page: id and name only |
GET /api/auth/idp/{id}/start | Begin a sign-in (redirect) |
GET /api/auth/idp/{id}/callback | Where the provider sends the browser back |