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

# Introduction

> Configure enterprise single sign-on (SSO) over OpenID Connect for your Auth application.

Enterprise SSO over OpenID Connect (OIDC) lets your users authenticate through their organization's Identity Provider (IdP), such as Okta, Google Workspace, or Microsoft Entra ID. It reuses the same OAuth authorization-code flow as [social login](/session/documentation/integration-guide/social-login/introduction) — the difference is tenancy: instead of one global "Sign in with Google" button, you register one or more **enterprise connections**, each scoped to a customer's IdP and bound to their email domains.

## Prerequisites

Before you start, make sure you have:

* A Prelude account with access to Prelude Auth
* An **Application ID** (`appID`) — see [Applications](/session/documentation/applications)
* Your **Management API key** for backend calls
* OAuth credentials from the customer's IdP application (client ID, client secret, and for Okta the issuer URL)
* Admin access to the Identity Provider you want to connect

## Supported providers

Enterprise connections are available for the OIDC-capable providers:

| Provider           | Identifier  |
| ------------------ | ----------- |
| Okta               | `okta`      |
| Google Workspace   | `google`    |
| Microsoft Entra ID | `microsoft` |

## Personal vs enterprise connections

Each OAuth provider config carries a `type`:

* **`personal`** (the default) — the classic social-login button. At most one per provider, addressed as `…/oauth/{provider}`.
* **`enterprise`** — a per-customer OIDC connection. An app can hold any number of them per provider, each addressed as `…/oauth/{provider}/{connection_id}`.

Creating an enterprise connection never conflicts with a personal one for the same provider. Two enterprise connections conflict only if they claim the same email domain — a domain must resolve to exactly one connection.

<Note>
  The `type` of a connection is immutable. To switch between personal and
  enterprise, delete the config and create a new one.
</Note>

## Create a connection

Enterprise connections are managed through the same endpoint as social login, with `type` set to `enterprise` and an `enterprise` block:

```bash theme={null}
curl -X POST https://api.prelude.so/v2/session/apps/{appID}/config/login/oauth/okta \
  -H "Authorization: Bearer $PRELUDE_MANAGEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "enterprise",
    "client_id": "0oa1b2c3d4EXAMPLE",
    "client_secret": "…",
    "enabled": true,
    "scopes": ["openid", "email", "profile"],
    "enterprise": {
      "issuer_url": "https://acme.okta.com/oauth2/default",
      "email_domain_allowlist": ["acme.com"],
      "jit_provisioning": true,
      "allow_email_account_merge": true,
      "sync_profile_on_login": true,
      "default_redirect_uri": "https://acme.yourapp.com/callback"
    }
  }'
```

The response includes the generated `connection_id` (an `ocon_…` identifier). Use it to update, delete, or launch the connection.

| Method                                   | Path                                                     |
| ---------------------------------------- | -------------------------------------------------------- |
| List all configs (personal + enterprise) | `GET …/config/login/oauth`                               |
| Create                                   | `POST …/config/login/oauth/{provider}`                   |
| Update                                   | `PUT …/config/login/oauth/{provider}/{connection_id}`    |
| Delete                                   | `DELETE …/config/login/oauth/{provider}/{connection_id}` |

## Connection behavior

The `enterprise` block controls provisioning, login, and profile syncing:

| Field                       | Type      | Description                                                                                                                                                                                    |
| --------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `issuer_url`                | string    | The IdP's OIDC issuer (https). Required. For Okta this is the authorization server URL, e.g. `https://acme.okta.com/oauth2/default`.                                                           |
| `email_domain_allowlist`    | string\[] | Email domains this connection covers. **Required and non-empty.** It is the domain→connection binding used for email-resolved login, and every login's IdP-asserted email must fall within it. |
| `jit_provisioning`          | boolean   | When `true`, just-in-time provisions a new user on first login. When `false`, a user with no existing match is rejected.                                                                       |
| `allow_email_account_merge` | boolean   | When `true`, links the OIDC identifier to an existing user that owns the same email.                                                                                                           |
| `enforce_login`             | boolean   | Reserved for future parity with SAML enforcement.                                                                                                                                              |
| `sync_profile_on_login`     | boolean   | When `true`, refreshes `name`, `given_name`, `family_name`, and `picture` from the IdP on every login instead of only at first provisioning.                                                   |
| `default_redirect_uri`      | string    | Redirect URI used when a login flow omits `redirect_uri`. Must be allowlisted for your app.                                                                                                    |
| `claim_mapping`             | object    | Optional overrides for the id\_token claim names used to read `email`, `given_name`, `family_name`, and arbitrary `custom` claims. Sensible defaults apply when omitted.                       |

## How the flow works

An enterprise login is the same authorization-code flow as social login:

1. **Initiate** — Your app starts the flow by **email** (resolved to a connection via its domain allowlist) or by **connection id**. Prelude derives the provider from the connection and redirects the user to the IdP.
2. **Callback** — After authenticating, the IdP redirects back and Prelude redirects to your app with a `challenge_token`.
3. **Finalize** — Your app finalizes the `challenge_token`, exactly as for [social login](/session/documentation/frontend-sdks/web/social-login).

The key difference from personal social login is the **email domain check**: because the connection is pre-bound to one or more domains, a matching domain is proof enough of the user's organization, so there is no `verify_email` OTP step. A login whose IdP email falls outside the allowlist is refused (`oauth_email_domain_not_allowed`).

<Note>
  Enterprise identities are scoped per connection. The stored identifier is
  `oauth:{provider}:{connection_id}`, so the same provider subject seen through
  two different connections never collapses onto one account.
</Note>

## Getting the email from Microsoft Entra ID

Microsoft only includes an `email` claim in the id\_token when the signed-in user has an email set on their profile, or when the app registration requests the `email` **optional claim**. If Entra logins reach the callback without an email — which an enterprise connection rejects, since it cannot match a domain — add the optional claim:

1. In the Azure portal, open **App registrations → your app → Token configuration**.
2. Choose **Add optional claim**, select **ID**, and add **email**.
3. Request the `email` scope in the connection's `scopes`.

For accounts whose email is not part of the tenant directory, also ensure the user has a verified email on their Microsoft profile.

## What's next?

<CardGroup cols={2}>
  <Card title="Web integration" icon="js" href="/session/documentation/frontend-sdks/web/enterprise-oidc">
    Start an enterprise SSO login from the JavaScript SDK.
  </Card>

  <Card title="Social login" icon="users" href="/session/documentation/integration-guide/social-login/introduction">
    Configure classic social-login providers.
  </Card>
</CardGroup>
