# Enterprise SSO

Configure Single Sign-On for your organization

Enterprise Single Sign-On (SSO) lets organization members authenticate through your identity
provider (IdP) over OIDC. This guide walks through configuring an OIDC application, registering it
on the organization, verifying a domain so members are auto-routed to SSO, and choosing the right
authentication requirement for the organization.

## Overview

Enterprise SSO provides:

- **OIDC sign-in** through your identity provider (Okta, Azure AD, Google, or any compliant OIDC IdP)
- **Email-domain auto-redirect:** members signing in from a verified company domain are sent to your IdP automatically
- **Organization-level authentication policy** that can require MFA or restrict members to SSO only

## Prerequisites

Before configuring SSO:

1. **Insights plan** for the organization — Insights grants both SSO and custom-domain verification.
2. **Owner role** in the organization — SSO and security settings are restricted to the organization owner.
3. **An OIDC-compliant identity provider** with the ability to register a confidential client.
4. **A domain you can prove ownership of** if you want email-based auto-redirect or stricter enforcement.

Organizations without the Insights entitlement receive the normal public API error shape with HTTP
`403` and `code: "FORBIDDEN"`; provider credentials are not saved.

## Step 1 — Configure your identity provider

Create an OIDC application in your IdP with these settings:

| Setting               | Value                                            |
| --------------------- | ------------------------------------------------ |
| Sign-in redirect URI  | `https://api.indexhog.com/api/auth/sso/callback` |
| Sign-out redirect URI | `https://indexhog.com`                           |
| Grant type            | Authorization Code                               |
| Response type         | Code                                             |
| Scopes                | `openid`, `profile`, `email`                     |

> **Warning:** **The callback URL is shared across all organizations.** Every SSO callback is routed through a
> single `/api/auth/sso/callback` endpoint; the `state` parameter identifies which organization
> initiated the flow. Configuring an org-specific callback will produce a redirect-URI-mismatch
> error from your IdP.

### Identity provider examples

**Okta**

1. Create a new Web Application
2. Set the Sign-in redirect URI shown above
3. Enable the Authorization Code grant
4. Copy the Client ID and Client Secret

**Azure AD**

1. Register a new application
2. Add a Web platform redirect URI
3. Create a client secret
4. The Issuer URL is `https://login.microsoftonline.com/{tenant-id}/v2.0`

## Step 2 — Register SSO on the organization

Once your IdP application exists, send the credentials to the API. SSO settings live under the
organization, so the call is tenant-scoped — the organization id sits in the URL path and
authentication is the same as any other API request (session cookie or `X-Api-Key`).

**curl**

```bash
ORG_ID="01HZ3K5R4X9Y2V6QF8TJ7W0CDN"
curl -X PUT "https://api.indexhog.com/api/user/organizations/${ORG_ID}/settings/sso" \
-H "X-Api-Key: tkn_AbCdEf..." \
-H "Content-Type: application/json" \
-d '{
  "provider": "okta",
  "issuerUrl": "https://your-org.okta.com",
  "clientId": "your-client-id",
  "clientSecret": "your-secret"
}'
```

**JavaScript**

```javascript
const apiKey = '<your-api-key>'; // load this from a secure env var — never hardcode
const organizationId = '01HZ3K5R4X9Y2V6QF8TJ7W0CDN';
const res = await fetch(
`https://api.indexhog.com/api/user/organizations/${organizationId}/settings/sso`,
{
  method: 'PUT',
  headers: {
    'X-Api-Key': apiKey,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    provider: 'okta',
    issuerUrl: 'https://your-org.okta.com',
    clientId: 'your-client-id',
    clientSecret: 'your-secret',
  }),
},
);
```

The request body has exactly four fields:

| Field          | Description                                                         |
| -------------- | ------------------------------------------------------------------- |
| `provider`     | The IdP family (e.g. `okta`, `azure`, `google`, `oidc`)             |
| `issuerUrl`    | The OIDC issuer / discovery base URL (no `/.well-known/...` suffix) |
| `clientId`     | Client ID from the IdP application                                  |
| `clientSecret` | Client secret from the IdP application — stored encrypted at rest   |

Email-domain auto-redirect is configured separately on the verified domain (Step 3), not on the SSO settings body. To remove SSO, send `DELETE` to the same URL.

## Step 3 — Verify your domain

Domains are managed as a separate resource. Once a domain is verified, members signing in with a
matching email address are auto-routed to your IdP, and the domain becomes eligible for auto-join
and SSO enforcement.

1. Add the domain via `POST /api/user/organizations/{organizationId}/domains` — the response includes a DNS TXT record name and value.
2. Add that record to your DNS provider.
3. Trigger verification with `POST /api/user/organizations/{organizationId}/domains/{domainId}/verify`.

**curl**

```bash
ORG_ID="01HZ3K5R4X9Y2V6QF8TJ7W0CDN"

# Add a domain

curl -X POST "https://api.indexhog.com/api/user/organizations/${ORG_ID}/domains" \
-H "X-Api-Key: tkn_AbCdEf..." \
-H "Content-Type: application/json" \
-d '{ "domain": "yourcompany.com" }'

# After publishing the DNS TXT record, run verification

DOMAIN_ID="01HZ3KA7V0J9Z8Q2G5B1T7XWDN"
curl -X POST "https://api.indexhog.com/api/user/organizations/${ORG_ID}/domains/${DOMAIN_ID}/verify" \
-H "X-Api-Key: tkn_AbCdEf..."
```

**JavaScript**

```javascript
const apiKey = '<your-api-key>'; // load this from a secure env var — never hardcode
const organizationId = '01HZ3K5R4X9Y2V6QF8TJ7W0CDN';

// Add the domain
const added = await fetch(
`https://api.indexhog.com/api/user/organizations/${organizationId}/domains`,
{
method: 'POST',
headers: {
'X-Api-Key': apiKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({ domain: 'yourcompany.com' }),
},
);

// After publishing the DNS TXT record, run verification
const domainId = '01HZ3KA7V0J9Z8Q2G5B1T7XWDN';
const verified = await fetch(
`https://api.indexhog.com/api/user/organizations/${organizationId}/domains/${domainId}/verify`,
{
method: 'POST',
headers: { 'X-Api-Key': apiKey },
},
);
```

## Step 4 — Choose an authentication requirement

Each organization carries a security configuration with two fields. Update them via
`PUT /api/user/organizations/{organizationId}/settings/security`.

```json
PUT /api/user/organizations/{organizationId}/settings/security
{
"authRequired": "any",
"requireMfa": false
}
```

### `authRequired`

| Value | Who can sign in                 | What it means in practice                                                              |
| ----- | ------------------------------- | -------------------------------------------------------------------------------------- |
| `any` | Any auth method                 | Default. Members may sign in with password, magic link, passkey, social, or SSO.       |
| `mfa` | Any method, but MFA is required | Sessions must complete MFA before tenant-scoped requests succeed.                      |
| `sso` | SSO only                        | Password, magic link, and social sign-in are blocked for members of this organization. |

### `requireMfa`

A boolean that enforces MFA on session sign-ins independently of `authRequired`. PATs are not subject to MFA — scope and pin them carefully to keep automated access safe.

## Test the configuration

Trigger a server-side validation of the configured issuer URL with
`POST /api/user/organizations/{organizationId}/settings/sso/test`. It performs OIDC discovery
against the saved issuer and returns whether the configuration is reachable. The endpoint takes
no body.

```json
{
"success": true,
"status": 200,
"code": "OK",
"message": "SSO configuration is valid",
"data": { "success": true, "message": "SSO configuration is valid" }
}
```

> **Warning:** **Validate before raising the requirement to `sso`.** If discovery fails after enforcement is in
> place, members can be locked out until the requirement is relaxed or the configuration is fixed.

## SSO login flow

Users with verified-domain emails follow this flow:

1. User submits an email at sign-in.
2. The sign-in flow detects a verified domain pointing to an SSO-enabled organization.
3. The client is redirected to `/api/auth/sso/{slug}/login`, which forwards to your IdP.
4. After IdP authentication, the IdP redirects back to `/api/auth/sso/callback`.
5. A session is established and the user is returned to the app.

`/api/auth/sso/{slug}/login` is the canonical entry point — `{slug}` is the organization's slug, available on `GET /api/user/organizations`.

## Auto-join configuration

Each verified domain carries an auto-join policy. Update it via the domain settings endpoint to
enable automatic organization membership for users with matching email domains. Optionally require
admin approval before joining — pending requests can then be reviewed via the organization members
API.

## Troubleshooting

| Issue                           | Solution                                                                                                |
| ------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Redirect URI mismatch           | Confirm the callback URL is exactly `https://api.indexhog.com/api/auth/sso/callback`                    |
| Invalid issuer                  | The issuer URL is the discovery base; the API appends `/.well-known/openid-configuration` automatically |
| Domain auto-redirect not firing | The domain must be `verified` (not `pending`) and the organization must have SSO configured             |
| Members locked out              | Lower `authRequired` from `sso` to `any` (or `mfa`) to restore access while you fix the configuration   |

## Best Practices

- **Configure and test before enforcing:** Set `authRequired` to `any` while you validate the IdP, then raise it once members can sign in.
- **Keep at least one non-SSO admin during rollout:** A backup credential prevents lockouts if the IdP misbehaves.
- **Use a verified domain for auto-redirect:** Without it, SSO works but members must navigate to the SSO login URL manually.
- **Treat the client secret as sensitive:** It is stored encrypted at rest, but anyone who can read it can sign in as your application.
- **Document SSO sign-in for your team:** Include the email they should use and the expected IdP screen to reduce support traffic.

## Next Steps

After setting up SSO:

1. **Configure API keys:** Set up [API Keys](/docs/api-reference/api-keys) for integrations.
2. **Review security settings:** Manage settings via the [Organizations API](/docs/api-reference/organizations).
3. **Manage domains:** Add additional domains via the [Organizations API](/docs/api-reference/organizations).