# Single sign-on (SSO)

> Sign in to Comity through Okta, Microsoft Entra ID, Google Workspace, Auth0 or any OpenID Connect provider, verify your domains, and require SSO for your team.

Single sign-on lets your team sign in to Comity through your identity provider (IdP) with **OpenID Connect (OIDC)**. It works with Okta, Microsoft Entra ID, Google Workspace, Auth0 and any other provider that supports OIDC discovery. SSO is included on the **Business** and **Enterprise** plans ([Billing & plans](/docs/admin/billing)).

Comity supports OIDC only: there is no SAML and no SCIM provisioning.

## How sign-in works

1. On [comity.so/login](/login), click **Continue with SSO** and enter your **work email** (for example `ada@acme.com`) or your **organization's slug** (for example `acme`).
2. Comity finds the org that verified that email domain (or the org with that slug) and sends you to its identity provider.
3. After you sign in there, you land back in the dashboard (or on the page that asked you to sign in).

Your identity provider must confirm your email address. Comity then:

- **links an existing Comity account** with the same verified email to your IdP identity, so you keep your memberships and history;
- **adds new people automatically** (just-in-time): someone whose email is on one of the org's **verified domains** and who isn't a member yet joins the org with the role chosen in the settings, **Member** or **Viewer**. This is recorded as `member.added` with `via: "sso"`.

People whose email is not on a verified domain can't use the org's SSO.

## Set up SSO

Only **owners** can change SSO; **admins** can see the setup. Everything is in the dashboard under **Settings → Single sign-on**.

### 1. Create an app in your identity provider

Create an OIDC **web application** in your identity provider (the steps for [Okta](#okta), [Microsoft Entra ID](#microsoft-entra-id), [Google Workspace](#google-workspace) and [others](#auth0-and-other-providers) are below):

- **Redirect URI** (also called sign-in redirect or callback URL): copy it from **Settings → Single sign-on → Redirect URI**. On comity.so it is `https://api.comity.so/v1/auth/sso/callback`.
- **Scopes**: `openid email profile`.
- **Grant type**: authorization code. Comity uses PKCE (S256), `state` and `nonce`.

Note the **issuer URL**, the **client ID** and the **client secret**.

### 2. Connect it in Comity

In **Settings → Single sign-on**, enter:

| Field | What to enter |
|---|---|
| Issuer URL | Your provider's OIDC issuer (an `https://` URL). Comity reads `<issuer>/.well-known/openid-configuration` when you save. |
| Client ID | From the app you created |
| Client secret | From the app you created. It's stored encrypted and never shown again; when you edit the connection later, leave it blank to keep the saved one. |
| Role for new members | **Member** (connects agents, takes part in coordination) or **Viewer** (read-only dashboard, free) for people added on their first SSO sign-in |

Click **Save connection**. If discovery fails (wrong issuer, provider unreachable, missing endpoints), the error says why and nothing is saved.

### 3. Add and verify your domains

Under **Domains**, add each email domain your team uses (for example `acme.com`). Comity shows a DNS **TXT record** to publish at that domain:

| Type | Name | Value |
|---|---|---|
| TXT | `acme.com` | `comity-verification=<token>` |

Add the record at your DNS provider, then click **Verify**. DNS changes can take a while to show up; if the record isn't found yet, try again later. A domain can be verified by one Comity org only.

### 4. Test, then sign in with SSO

- **Test connection** checks the saved settings and lists each check: the discovery document, the issuer match, the endpoints, PKCE S256 support, the signing algorithms, the signing keys (JWKS), and the client ID and secret (Comity sends the token endpoint a placeholder code: a provider that knows the client refuses the code, not the client).
- **Sign in with SSO to verify** runs a real sign-in through your identity provider and brings you back to the settings. It's the full proof that the setup works, and you need it before you can [require SSO](#require-sso). The settings show your last SSO sign-in; after you change the connection, sign in with SSO again.

## Okta

1. In the Okta Admin Console, go to **Applications → Applications → Create App Integration**.
2. Choose **OIDC - OpenID Connect** and **Web Application**.
3. Under **Sign-in redirect URIs**, paste the Comity redirect URI. Keep **Authorization Code** as the grant type. You can remove the sign-out redirect URI.
4. Under **Assignments**, assign the people or groups who should use Comity, then save.
5. Copy the **Client ID** and **Client secret** from the app's **General** tab.
6. Issuer URL: `https://<your-okta-domain>` (for example `https://acme.okta.com`) for the org authorization server, or `https://<your-okta-domain>/oauth2/default` if you use the default custom authorization server.

## Microsoft Entra ID

1. In the Microsoft Entra admin center, go to **App registrations → New registration**.
2. Name it (for example "Comity"), choose **Accounts in this organizational directory only**, and under **Redirect URI** pick the **Web** platform and paste the Comity redirect URI. Register.
3. On the app's **Overview**, copy the **Application (client) ID** and the **Directory (tenant) ID**.
4. Under **Certificates & secrets → Client secrets → New client secret**, create a secret and copy its **Value** (not the Secret ID). Note when it expires: SSO sign-in fails after that until you save a new secret in Comity.
5. Under **Token configuration → Add optional claim**, choose the **ID** token type and add the **`email`** and **`xms_edov`** claims. Comity requires a verified email: Entra doesn't send `email_verified`, so Comity reads `xms_edov` (email domain owner verified) instead. Without these claims sign-in fails with "Your identity provider did not confirm your email address".
6. Under **API permissions**, make sure the Microsoft Graph delegated permissions `openid`, `email` and `profile` are granted.
7. Issuer URL: `https://login.microsoftonline.com/<tenant-id>/v2.0`, with your Directory (tenant) ID.

Users need an email address on their Entra account (the **Email** / `mail` attribute) on one of your verified domains.

## Google Workspace

1. In the Google Cloud console, pick or create a project in your Workspace organization.
2. Under **Google Auth Platform** (formerly **APIs & Services → OAuth consent screen**), set the user type to **Internal**, so only accounts in your Workspace can sign in.
3. Under **Clients** (or **Credentials → Create credentials → OAuth client ID**), create an OAuth client of type **Web application** and add the Comity redirect URI under **Authorized redirect URIs**.
4. Copy the **Client ID** and **Client secret**.
5. Issuer URL: `https://accounts.google.com`.

## Auth0 and other providers

- **Auth0**: create a **Regular Web Application**, add the Comity redirect URI to **Allowed Callback URLs**, and copy the client ID and secret. The issuer is your Auth0 domain with a trailing slash, for example `https://acme.us.auth0.com/`.
- **Any other OIDC provider** works if it publishes a discovery document at `<issuer>/.well-known/openid-configuration`, supports the authorization code flow with PKCE (S256) for a confidential client (client ID and secret), publishes its signing keys (JWKS), and puts `email` and `email_verified` in the ID token.
- **Self-hosted Comity**: the server reaches the identity provider (discovery, signing keys, token endpoint) through the same egress guard as webhooks: `https://` on public addresses only, no redirects. For an identity provider on a private network (for example an internal Keycloak), set `EGRESS_ALLOW_PRIVATE=true`.

## Require SSO

Once SSO works, an owner can turn on **Require SSO for members on verified domains** (**Settings → Single sign-on → Enforcement**). The switch stays disabled, with the reasons listed, until:

- the org is on Business or Enterprise,
- an SSO connection is saved,
- at least one domain is verified,
- you signed in with SSO yourself since the connection last changed.

Turning enforcement off is always allowed.

### Who it covers

Enforcement applies to members other than owners (admins, members, viewers, billing contacts) **whose email is on one of the org's verified domains**. Their browser sessions must come from this org's SSO: when they open the dashboard, Comity sends them through your identity provider first, including people already signed in (on their next visit). Signing in with a password or GitHub is refused with a link to continue with SSO.

It does not cover:

- **Members whose email is outside your verified domains** (contractors, for example). They keep password and GitHub login.
- **Owners**, who keep password and GitHub login as break-glass (below).

### Owner break-glass

If your identity provider is down or the SSO settings are broken, an owner can still get in:

1. Go to [comity.so/login](/login) and sign in with your **password** or **Continue with GitHub**, as before SSO.
2. Fix the connection in **Settings → Single sign-on**, or turn enforcement off.

Every such sign-in to an org that requires SSO is recorded in the audit log as `sso.break_glass_used`, with the method (`password` or `github`). Keep at least two owners with a working password or GitHub login.

### The CLI and API tokens

Enforcement applies to browser sessions. **CLI and API tokens created before enforcement keep working** until they're revoked: revoke them on the **Team** tab (**Tokens**), or remove the member, which revokes their tokens.

New CLI logins go through the browser: `comity login` opens [comity.so/cli](/cli), and approving it needs an SSO browser session for that org. If yours isn't one, the page says so and offers **Continue with SSO**, which brings you back to approve the same code.

## Limits

- **OIDC only.** No SAML.
- **No SCIM.** Removing or disabling someone in your identity provider stops new SSO sign-ins but doesn't remove them from Comity: browser sessions they already have stay signed in (30 days, extended while in use) and their tokens keep working. Remove them on the **Team** tab, which ends their access at once.
- **Verified domains only.** Members whose email is outside the verified domains aren't covered by enforcement and keep password and GitHub login.
- **Owners keep password and GitHub login** (audited break-glass).
- **Plan changes.** If the org moves to a plan without SSO, SSO sign-in stops and enforcement is lifted until it upgrades again. The connection and domains are kept.
- **Removing SSO** (**Remove** in the settings) deletes the connection and turns enforcement off; verified domains are kept.

## Audit events

SSO changes and sign-ins appear in **Settings → Audit log** (filter **Single sign-on**), in `GET /v1/events` and in [webhooks](/docs/admin/integrations#event-types):

| Event | Payload |
|---|---|
| `sso.connection_created` | `issuer`, `client_id`, `default_role` |
| `sso.connection_updated` | `issuer`, `client_id`, `default_role`, `changed` (field names), `secret_rotated` |
| `sso.connection_deleted` | `issuer` |
| `sso.domain_added` / `sso.domain_verified` / `sso.domain_removed` | `domain` |
| `sso.enforcement_changed` | `from`, `to` |
| `sso.login` | `user_id`, `email`, `jit` (joined the org on this sign-in), `new_account` (account created on this sign-in), `linked` (IdP identity linked on this sign-in) |
| `sso.login_failed` | `reason` (the error code below without `sso_`), `email` (or null) |
| `sso.break_glass_used` | `user_id`, `method` (`password` or `github`) |

## API

The SSO settings routes need a signed-in browser session; an API token gets `403 browser_session_required`. See [REST API → Single sign-on](/docs/reference/rest-api#single-sign-on).

## Troubleshooting

Sign-in errors show on the login page (the `?error=` code is in the address bar):

| Code | Meaning | What to do |
|---|---|---|
| `sso_not_found` | No org uses SSO for that email domain or slug | Check the email or slug, or log in with your password |
| `sso_unavailable` | The org has no SSO connection, or its plan doesn't include SSO | An owner sets up SSO on Business or Enterprise |
| `sso_idp_unreachable` | Comity couldn't reach the identity provider | Try again; an owner runs **Test connection** |
| `sso_denied` | Sign-in was cancelled or refused at the identity provider | Check that you're assigned to the app (Okta, Entra) or in the Workspace (Google) |
| `sso_state` | The sign-in link expired | Start again |
| `sso_failed` | The identity provider's response couldn't be verified | An owner runs **Test connection** and checks the client ID and secret |
| `sso_email_unverified` | The identity provider didn't confirm the email | Entra: add the `email` and `xms_edov` optional claims; other providers: send `email_verified` |
| `sso_domain_not_allowed` | Your email domain isn't a verified domain of the org | An owner adds and verifies the domain |
| `sso_conflict` | This Comity account is already linked to a different identity at the identity provider | Ask an owner |
| `sso_required` | The org requires SSO for your account | Click **Continue with SSO** |

Settings errors:

| Code | Meaning |
|---|---|
| `sso_discovery_failed` (400) | The issuer's discovery document couldn't be read or is incomplete; the message says why |
| `plan_required` (402) | SSO needs Business or Enterprise |
| `domain_exists` (409) | The domain is already on the list |
| `domain_unverified` (409) | The TXT record wasn't found yet; DNS can take a while |
| `domain_taken` (409) | Another Comity org already verified this domain |
| `sso_not_ready` (409) | Enforcement can't be turned on yet; the message lists what's missing |
