Skip to main content
Version: 0.1

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​

  1. Register an application at your provider and note its client id and, if it issues one, its client secret.
  2. Click New provider.
  3. 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-configuration from it. It must be https://. Plain http:// is accepted only for localhost or a loopback address, for local testing.
  4. 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.
  5. 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).
  6. Under State, leave On switched on so the button shows on the sign-in page.
  7. Click Add provider.
  8. 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, so AUTH_BASE_URL in the gateway configuration must be the public address of the gateway.
  9. 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​

  1. Start. GET /api/auth/idp/{id}/start reads the provider's discovery document, makes a fresh state, nonce and PKCE verifier, stores them in an HttpOnly cookie scadex.idp scoped to /api/auth/idp that lives 10 minutes, and redirects to the provider's authorization endpoint with response_type=code, code_challenge_method=S256, the scopes, the state and the nonce.
  2. Callback. GET /api/auth/idp/{id}/callback?code&state spends the cookie, whatever happens next (one try per start). The state must match the cookie, the cookie must not have expired, and the provider must still be enabled.
  3. 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.
  4. 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; none and 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 its nonce must equal the one this browser started with. It must name a subject (sub) and an email with an @. The name is taken from name, then preferred_username, then the part of the email before @.
  5. 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​

SituationResult
The provider's subject (sub) was linked to a gateway account beforeThat account signs in. The email is not consulted
New subject, and the token does not say email_verified: trueRefused: email_not_verified
New subject, and Email domains are set but the email's domain is not among themRefused: domain_not_allowed
New subject, email matches an existing account, Link accounts offRefused: account_exists
New subject, email matches an existing administratorRefused: account_exists, whatever Link accounts says
New subject, email matches another existing account, Link accounts onThe subject is linked to that account, which signs in with its current role
New subject, no account with that emailA 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.

RoutePurpose
GET /api/v1/admin/identity-providersList providers, each with redirectUri and hasSecret
POST /api/v1/admin/identity-providersCreate: {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}/testRead the discovery document: {ok, authorizationEndpoint} or {ok: false, error}
GET /api/auth/idpEnabled providers for the login page: id and name only
GET /api/auth/idp/{id}/startBegin a sign-in (redirect)
GET /api/auth/idp/{id}/callbackWhere the provider sends the browser back