---
title: "Identity providers"
description: "Federate your directory with Okta, Microsoft Entra ID, Google Workspace or another OIDC or SAML identity provider."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.ezghcloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Identity providers

An identity provider is an upstream OIDC or SAML 2.0 provider, such as Okta, Microsoft Entra ID or
Google Workspace, that your people already sign in with. You route [verified email
domains](/sso/domains) to it: when someone enters an email address in one of those domains at your
portal, they're sent there to sign in, and come back signed in to EZGH SSO. Your applications
still trust only EZGH SSO.

A directory can have up to 10 identity providers. You need `sso.identityProviders.create`.

## How federated sign-in works

1. The person enters their email address at `https://<alias>.sso.ezgh.app`. Its domain is
   verified and routed to an identity provider, so they're sent there. Every address in the
   domain gets the same answer.
2. They sign in at the identity provider, which sends them back (OIDC: authorization code with
   PKCE; SAML: SP-initiated, HTTP-POST to the ACS).
3. EZGH SSO checks the response and finds the user by the identity provider's subject (OIDC
   `sub`, SAML `NameID`):
- **Already linked:** that user signs in. The email address is never used to find them again,
     so a changed email at the identity provider doesn't move the link.
- **First sign-in:** the asserted email's domain must be verified and routed to this identity
     provider. A user with that email in the directory is linked (a `pendingActivation` user
     becomes `active`; a suspended user is refused). Otherwise a new user is created with
     `source: jit` (just in time), counted against your user quota.
- **With [SCIM](/sso/scim):** while a SCIM token names the identity provider, only users its
     SCIM provisioned can sign in through it, and nobody is created on first sign-in.
4. [Group mappings](#group-mappings) are applied, and the session starts.

Users created this way have no password, TOTP or passkeys, and sign in only through their
identity provider. Suspending or deleting them in EZGH SSO works as for hosted users. Refused
sign-ins show an error page on the portal and are recorded in [Trails](/sso/trails).

## Add an identity provider

Add and verify the domains first, then create the identity provider, then route the domains to
it with `domains`. Each domain routes to one identity provider at most (`409 domain_in_use`),
and routes only while it's verified.

In the console, use **EZGH SSO** > **Identity providers**. With the CLI:

```sh
ezgh sso identity-providers create --name Okta --protocol oidc --issuer https://acme.okta.com \
  --client-id 0oa1b2c3 --client-secret-stdin --domains acme.com < okta-secret.txt
ezgh sso identity-providers get Okta
```

With the API, for each protocol:

### OIDC

Register an application at your provider, a web application with a client secret, and give it
this redirect URI:

```text
https://<alias>.sso.ezgh.app/federation/oidc/callback
```

Then create the identity provider:

```sh
curl -X POST https://sso.ezghcloud.com/v1/organizations/$ORG_ID/sso/identity-providers \
  -H "Authorization: Bearer $EZGH_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "name": "Okta",
    "protocol": "oidc",
    "oidc": { "issuer": "https://acme.okta.com", "clientId": "0oa…", "clientSecret": "…" },
    "domains": ["acme.com"]
  }'
```

| `oidc` field | Description | Default |
| --- | --- | --- |
| `issuer` | The provider's https issuer. Its `/.well-known/openid-configuration` must name exactly this issuer. | Required |
| `clientId` | The client ID you registered. | Required |
| `clientSecret` | Write-only: it's never shown again or recorded. `clientSecretSet` says whether one is set; `null` removes it. | |
| `tokenEndpointAuthMethod` | `client_secret_basic`, `client_secret_post`, or `none` for a public client. | `client_secret_basic` with a secret, else `none` |
| `scopes` | 1 to 20 scopes, including `openid`. | `openid email profile` |
| `emailClaim` | The ID token claim with the email address. An email marked `email_verified: false` isn't used. | `email` |
| `groupsClaim` | The ID token claim with the person's groups. | `groups` |

The answer adds `redirectUri` and, once fetched, the validated `discovery`. ID tokens must be
signed with RS256–512, PS256–512 or ES256–512.
### SAML

SAML needs the identity provider's metadata to create it, and the service provider values your
identity provider needs contain its ID. So:

1. Create a SAML application at your identity provider. If it insists on an ACS URL and entity ID
before it gives you metadata, enter temporary values.
2. Create the identity provider with `metadataUrl` (the provider's metadata URL) or
`metadataXml` (the metadata document, up to 256 KiB):

```sh
curl -X POST https://sso.ezghcloud.com/v1/organizations/$ORG_ID/sso/identity-providers \
  -H "Authorization: Bearer $EZGH_API_KEY" -H "Content-Type: application/json" \
  -d '{ "name": "Entra ID", "protocol": "saml",
        "saml": { "metadataUrl": "https://login.microsoftonline.com/…/federationmetadata.xml?appid=…" } }'
```

3. Enter the answer's `saml.serviceProvider` at your identity provider: `entityId` as the entity
ID (audience), and `acsUrl` as the ACS (reply) URL. `metadataUrl` serves the same as SAML
metadata.

| Value | Form |
| --- | --- |
| Entity ID, metadata | `https://<alias>.sso.ezgh.app/federation/saml/metadata/<identity provider ID>` |
| ACS URL | `https://<alias>.sso.ezgh.app/federation/saml/acs/<identity provider ID>` |

4. Route the domains: [UpdateIdentityProvider](/sso-api/UpdateIdentityProvider/) with
`domains`.

| `saml` field | Description | Default |
| --- | --- | --- |
| `metadataUrl` | An https URL of the identity provider's metadata, fetched and refreshed. | One of the two is required |
| `metadataXml` | The metadata itself. It's checked at once and never refreshed: update it when the provider's certificate changes. | |
| `emailAttribute` | The attribute with the email address. Empty: the `NameID` when its format is email address, else a common email attribute (`email`, `mail`, `emailaddress`, the `…/claims/emailaddress` claim). | Empty |
| `groupsAttribute` | The attribute with the person's groups. | `groups` |

The metadata must describe one identity provider with an https HTTP-Redirect single sign-on URL
and 1 to 10 RSA (2048 bits or more) or ECDSA signing certificates. The answer shows its
`entityId`, `ssoUrl` and certificates with their expiry.

Requirements for responses:

- **Both the response and its assertion must be signed**, with SHA-256 or stronger (SHA-1 is
refused). Encrypted assertions are refused.
- Only SP-initiated sign-in: a response that EZGH SSO didn't ask for (IdP-initiated) is refused.
- The assertion must name the service provider's entity ID as its audience, and a `NameID` that
isn't transient.

### Metadata and keys

EZGH SSO fetches OIDC discovery and keys, or SAML metadata, when you create the identity provider
or change its issuer or metadata URL, then every 6 hours (every 15 minutes after a failure).
[RefreshIdentityProviderMetadata](/sso-api/RefreshIdentityProviderMetadata/) fetches within
seconds (`409 metadata_by_value` for SAML given as `metadataXml`). A failed fetch keeps the last
good copy and shows the reason in `metadata.error`. Until the first fetch succeeds, sign-in through
the identity provider is unavailable (`503 identity_provider_unavailable`). URLs must be public
https URLs.

## Group mappings

`groupMappings` adds people to directory groups from the groups the identity provider sends (the
OIDC `groupsClaim` or SAML `groupsAttribute`): up to 100 `{ "idpGroup", "groupId" }` pairs.
`idpGroup` is the value the provider sends (a name, or an ID for providers that send IDs), or `*`
for everyone who signs in through it. `groupId` is a directory group you created: groups pushed
by SCIM belong to SCIM.

Mappings are applied on every federated sign-in: the person is added to the mapped groups, and
removed from groups this identity provider added that no longer match. Memberships you added by
hand are left alone.

## MFA

With `trustUpstreamMfa` off (the default) in the [sign-in policy](/sso/directory#sign-in-policy),
`mfaRequired` applies only to hosted sign-in, and federated sessions count as single-factor.

With it on, EZGH SSO reads the identity provider's evidence of multi-factor sign-in: an OIDC `amr`
with `mfa` or two different methods, or a SAML authentication context for MFA (or Entra ID's
`authnmethodsreferences`). A sign-in with that evidence is multi-factor (`acr`
`urn:ezgh:sso:acr:mfa`), and when the directory requires MFA, a sign-in without it is refused.

## Provider guides

### Okta

**OIDC** (recommended):

1. In the Okta Admin Console, create an app integration: **OIDC - OpenID Connect**, **Web
Application**.
2. **Sign-in redirect URIs**: `https://<alias>.sso.ezgh.app/federation/oidc/callback`. Assign the
people or groups who should use it.
3. Copy the **Client ID** and **Client secret**. The issuer is your Okta domain, such as
`https://acme.okta.com` (or a custom authorization server's issuer).
4. For groups, add a groups claim named `groups` to the ID token (in the app's **Sign On**
settings, a groups claim filter). If your setup puts it behind the `groups` scope, add `groups`
to `scopes`.

**SAML:** create a **SAML 2.0** app integration. **Single sign-on URL** is the ACS URL and
**Audience URI** the entity ID; **Name ID format** `EmailAddress`, **Application username**
`Email`. Keep the response and the assertion signed. Add a group attribute statement named
`groups`. Use the app's metadata URL as `metadataUrl`.
### Microsoft Entra ID

**OIDC:**

1. In the Entra admin center, **App registrations** > **New registration**, with a **Web**
redirect URI `https://<alias>.sso.ezgh.app/federation/oidc/callback`.
2. **Certificates & secrets**: create a client secret. The client ID is the **Application
(client) ID**.
3. The issuer is `https://login.microsoftonline.com/<tenant ID>/v2.0`.
4. **Token configuration**: add the optional `email` claim to the ID token, and a groups claim.
Entra sends group object IDs, so use those as `idpGroup` in group mappings.

**SAML:**

1. **Enterprise applications** > **New application** > **Create your own application**
(non-gallery), then **Single sign-on** > **SAML**.
2. **Identifier (Entity ID)** is the entity ID and **Reply URL** the ACS URL.
3. Under **SAML Certificates**, set **Signing option** to **Sign SAML response and assertion**.
Entra signs only the assertion by default, which EZGH SSO refuses.
4. For groups, add a group claim, and set `groupsAttribute` to its name
(`http://schemas.microsoft.com/ws/2008/06/identity/claims/groups` unless you rename it).
5. Use the **App Federation Metadata Url** as `metadataUrl`.
### Google Workspace

**SAML** (sends groups):

1. In the Google Admin console, **Apps** > **Web and mobile apps** > **Add custom SAML app**.
2. Download the IdP metadata, and use it as `metadataXml` (Google doesn't publish a metadata
URL, so update it when Google's certificate changes).
3. **ACS URL** is the ACS URL and **Entity ID** the entity ID. Select **Signed response**.
**Name ID format** `EMAIL`, **Name ID** the primary email.
4. Under **Group membership**, map the groups to an app attribute named `groups`.
5. Turn the app on for the people who should use it.

**OIDC** (no groups): create an OAuth client in the Google Cloud console, type **Web
application**, with the redirect URI above. The issuer is `https://accounts.google.com`. Make the
OAuth consent screen **Internal** so only your Workspace's accounts can sign in. Google's ID
tokens carry no groups, so group mappings other than `*` don't apply.

## Provisioning

`provisioning` shows how people get into the directory through this identity provider:

- `{ "jit": true, "scim": false }`: people are created on their first sign-in.
- `{ "jit": false, "scim": true }`: a live directory SCIM token names this identity provider. Its
  SCIM provisions people, and only provisioned people sign in through it. See
  [Inbound SCIM](/sso/scim).

## Delete an identity provider

[DeleteIdentityProvider](/sso-api/DeleteIdentityProvider/) unroutes its domains (people in
them sign in hosted from then on), revokes the SCIM tokens that name it, removes the users' links
to it and the group memberships its mappings added, and ends federated sign-ins in progress. The
users stay in the directory, and users and groups its SCIM provisioned are unlocked.

Source: https://docs.ezghcloud.com/sso/identity-providers/index.mdx
