<!-- section: Auth API · status: building · source: docs/auth-api/10-enterprise-sso.md -->

> OIDC and SAML 2.0, registered by your customers themselves, with users joining an organization on arrival.


# Enterprise SSO

Your enterprise customers sign in with their own Okta, Entra or any OIDC or
SAML 2.0 identity provider — and set it up themselves, without emailing anyone a
certificate.

The shape is three deep, so it is worth naming: we host the service, you run an
application on it, and *your* customer is a company with a directory. An SSO
provider belongs to one of **your** organizations.

## 1. Declare the identity provider's hosts

Registering one makes our server fetch the provider's discovery document, so the
hosts have to be trusted in advance — otherwise `sso/register` would be "make
your server request any URL I name".

```bash Request
curl -X PATCH "$BASE/v1/auth/settings" \
  -H "authorization: Bearer $SK" \
  -H "content-type: application/json" \
  -d '{"ssoOrigins":["https://bigcorp.okta.com"]}'
```

**Every origin the discovery document resolves to must be listed, not just the
issuer.** Providers routinely put their token and JWKS endpoints on other hosts —
Google's issuer is `accounts.google.com` while its token endpoint is
`oauth2.googleapis.com` — and registration fails with
`discovery_untrusted_origin` naming the exact URL it refused.

These origins are trusted for SSO endpoints only. They never gain the right to
make browser requests against your API.

## 2. POST /v1/auth/sso/register

An organization owner or admin registers the provider. **Publishable** key and
their session.

```bash Request
curl -X POST "$BASE/v1/auth/sso/register" \
  -H "authorization: Bearer $PK" \
  -H "content-type: application/json" \
  -b "session cookie" \
  -d '{
    "providerId": "bigcorp-okta",
    "issuer": "https://bigcorp.okta.com",
    "domain": "bigcorp.com",
    "organizationId": "org_…",
    "oidcConfig": { "clientId": "…", "clientSecret": "…",
      "discoveryEndpoint": "https://bigcorp.okta.com/.well-known/openid-configuration" }
  }'
```

A plain `member` gets **403** — registering an identity provider decides who can
enter the organization, so it takes an owner or an admin.

## 3. POST /v1/auth/sign-in/sso

Users sign in by email domain.

```bash Request
curl -X POST "$BASE/v1/auth/sign-in/sso" \
  -H "authorization: Bearer $PK" \
  -H "content-type: application/json" \
  -d '{"email":"someone@bigcorp.com","callbackURL":"https://yourapp.com/"}'
```

Back comes a `url` to redirect to. Anyone arriving through that provider **joins
the linked organization automatically**, as a `member` — the point of enterprise
SSO is that staff arrive already belonging somewhere.

## SAML

The same registration call with `samlConfig` instead. The certificate and entry
point come from the IdP, so nothing is fetched and **no origins need declaring**.

Give the IdP administrator your service provider metadata:

```
GET $BASE/v1/auth/sso/saml2/sp/metadata?providerId=bigcorp-saml
```

It carries the ACS URL they need, which is ours:

```
$BASE/v1/auth/sso/saml2/sp/acs/bigcorp-saml
```

IdP-initiated sign-in works without extra setup — the callback handles both the
POST from the IdP and the browser's follow-up GET.

## Errors

| Status | Code | Means |
|---|---|---|
| `400` | `discovery_untrusted_origin` | A host in the discovery document is not in `ssoOrigins` |
| `403` | — | A `member` attempting to register a provider |
| `404` | — | No SSO provider matches that email domain |
