> For the complete documentation index, see [llms.txt](https://docs.controltheory.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.controltheory.com/controltheory-documentation/dstl8-docs/single-sign-on.md).

# Single Sign-On

Let your organization's members sign in to Dstl8 through your own identity provider.

Single sign-on (SSO) lets the members of your organization sign in to Dstl8 with the accounts they already use at work. Dstl8 connects to your identity provider through OpenID Connect, so it works with Microsoft Entra ID, Auth0, Okta and other OpenID Connect providers. You can also connect your organization's own Google or GitHub OAuth app.

You set up a connection once, as an organization admin, from **Organization Settings → SSO**.

### How SSO works

* **Invite-only.** Invite each user from **Organization Settings → Users → Invite User** before their first SSO sign-in. When they sign in through SSO for the first time, Dstl8 links their identity provider account to the invitation, and they keep the role and workspaces you gave them. Someone who wasn't invited can't get in through SSO, even if your identity provider lets them sign in.
* **Your organization's sign-in page.** Users sign in at `https://<org_id>.app.dstl8.ai/login` and click **Continue with** followed by the name of your OpenID Connect connection, or **Continue with Google** or **Continue with GitHub**.
* **Linked to the account, not the email.** After the first sign-in, Dstl8 recognizes the user by their identity provider account. Changing their email address in your identity provider later doesn't break the link.
* **Not enforced.** SSO is an extra way to sign in. Your users' other sign-in methods remain available.
* **Web only, for now.** The `dstl8` CLI can't sign in through SSO yet. Use an API token instead; see [CLI and automation](#cli-and-automation).

You need the **Admin** org role to set up SSO and invite users. You can't give anyone a role above your own, so only a **Superadmin** can invite another superadmin.

{% hint style="warning" %}
**Managing SSO is full access.** Whoever manages SSO connections chooses the identity provider that vouches for your users' email addresses, so they could sign in as any member of your organization, superadmins included. Give the **Admin** role only to people you trust with that.
{% endhint %}

### Your redirect URI

Every identity provider asks for the address it sends users back to after they sign in. For Dstl8, that's:

```
https://<org_id>.app.dstl8.ai/oauth/callback
```

To copy the exact value for your organization, go to **Organization Settings → SSO** and click **SSO Setup Info**. The **Redirect URI** shown there is the one to register.

### Set up Microsoft Entra ID

You'll need permission to register applications in your Entra tenant and to grant admin consent. All steps take place in the [Microsoft Entra admin center](https://entra.microsoft.com) or the Azure portal.

#### 1. Register an application

1. Go to **App registrations** and click **New registration**.
2. Give it a name your users will recognize, such as `Dstl8`.
3. Under **Supported account types**, choose **Accounts in this organizational directory only (Single tenant)**.
4. Under **Redirect URI**, select the **Web** platform and paste your [redirect URI](#your-redirect-uri).
5. Click **Register**.

{% hint style="warning" %}
**Choose Web, not Single-page application.** Dstl8 completes the sign-in on its server, which Entra only allows for the **Web** platform. A redirect URI registered as a single-page application fails with `AADSTS9002327`. If you added it under the wrong platform, go to **Authentication**, remove it, and add it again under **Web**.
{% endhint %}

#### 2. Create a client secret

1. In the app registration, go to **Certificates & secrets → Client secrets** and click **New client secret**.
2. Choose an expiry. Entra allows at most 24 months.
3. Copy the secret's **Value** (not the **Secret ID**) right away. Entra shows it only once.

{% hint style="warning" %}
**Note the expiry date.** When the secret expires, SSO sign-ins stop working. Set a reminder, and before that date create a new secret and enter it in your Dstl8 connection (see [Replacing a client secret](#replacing-a-client-secret)).
{% endhint %}

#### 3. Add the email claims

Entra doesn't include the user's email address in the ID token by default, and Dstl8 needs it to match users to their invitations.

1. Go to **Token configuration** and click **Add optional claim**.
2. Choose the **ID** token type, check `email` and `xms_edov`, and click **Add**.
3. If Entra asks to turn on the Microsoft Graph email permission, accept.

`xms_edov` tells Dstl8 whether the domain of the user's email address is verified in your tenant.

{% hint style="warning" %}
**Both claims are required.** In Entra a user's email address can be set to any value, so Dstl8 only trusts it when `xms_edov` confirms the domain is verified. Without `xms_edov`, no one can be matched to an invitation, and SSO sign-ins are refused. **Test Connection** tells you if it's missing.
{% endhint %}

#### 4. Grant API permissions

1. Go to **API permissions**, click **Add a permission**, and choose **Microsoft Graph → Delegated permissions**.
2. Check `openid`, `email` and `profile`, and click **Add permissions**.
3. Click **Grant admin consent for** your organization and confirm, so users aren't asked to consent one by one.

#### 5. Choose who can sign in (optional)

By default, anyone in your tenant can sign in to the app. To limit it to specific people:

1. Go to **Enterprise applications** and open the application with the same name.
2. Under **Properties**, set **Assignment required?** to **Yes** and click **Save**.
3. Under **Users and groups**, click **Add user/group** and assign the users or groups who should have access. Include yourself, so you can test the connection.

Users who aren't assigned see `AADSTS50105` when they try to sign in.

#### 6. Collect the values for Dstl8

On the app registration's **Overview** page, copy the **Application (client) ID** and the **Directory (tenant) ID**. Your issuer URL is built from the tenant ID:

```
https://login.microsoftonline.com/<Directory (tenant) ID>/v2.0
```

For example, `https://login.microsoftonline.com/7f1c2a9e-0b3d-4c8e-9a51-2d6e4f8b1c07/v2.0`. Dstl8 accepts only this form: issuers that use `common`, `organizations`, `consumers` or a domain name in place of the tenant ID are refused.

You now have everything Dstl8 needs: the issuer URL, the client ID, and the client secret's value. Continue with [Add the connection in Dstl8](#add-the-connection-in-dstl8).

{% hint style="info" %}
**Who can sign in through Entra.** Dstl8 uses the email address Entra sends, never the user principal name. Users without an email address (for example, accounts with no mailbox) can't sign in until they have one. Users whose email domain isn't verified in your tenant, including most guests, are refused on their first sign-in.
{% endhint %}

### Set up Auth0, Okta, Google or GitHub

{% tabs %}
{% tab title="Auth0" %}

1. In the Auth0 dashboard, go to **Applications → Applications** and click **Create Application**.
2. Choose **Regular Web Applications** and click **Create**.
3. On the **Settings** tab, paste your [redirect URI](#your-redirect-uri) into **Allowed Callback URLs** and click **Save**.
4. Under **Advanced Settings → OAuth**, make sure **JSON Web Token (JWT) Signature Algorithm** is **RS256** (the default).
5. Copy the **Client ID** and **Client Secret** from the **Settings** tab.

Your issuer URL is your Auth0 domain with a trailing slash, for example `https://<tenant>.auth0.com/` (some tenants include a region, such as `https://<tenant>.us.auth0.com/`). If you use a custom domain, use that instead: `https://login.example.com/`.

Users must have a verified email address in Auth0. Sign-ins from users whose email isn't verified are refused.
{% endtab %}

{% tab title="Okta" %}

1. In the Okta Admin Console, go to **Applications → Applications** and click **Create App Integration**.
2. Choose **OIDC - OpenID Connect** and **Web Application**, then click **Next**.
3. Keep the **Authorization Code** grant type, and paste your [redirect URI](#your-redirect-uri) into **Sign-in redirect URIs**.
4. Under **Assignments**, choose who can use the app, and click **Save**.
5. Copy the **Client ID** and **Client secret**.

Your issuer URL is your Okta domain, `https://<your-okta-domain>`, or, if you use the default custom authorization server, `https://<your-okta-domain>/oauth2/default`.

Users must have a verified email address in Okta.
{% endtab %}

{% tab title="Google or GitHub" %}
To use your organization's own Google or GitHub OAuth app instead of OpenID Connect:

* **Google:** in the Google Cloud console, create an OAuth client ID of type **Web application** and add your [redirect URI](#your-redirect-uri) under **Authorized redirect URIs**.
* **GitHub:** create an OAuth App in your GitHub organization's settings, set **Homepage URL** to `https://<org_id>.app.dstl8.ai` and **Authorization callback URL** to your redirect URI.

When you add the connection in Dstl8, choose **Google** or **GitHub** as the **Provider**. No issuer URL is needed. An organization can have one Google and one GitHub connection.

Like OpenID Connect connections, Google and GitHub connections must pass **Test Connection** before they can be enabled. A connection that was already enabled before this requirement keeps working, and you can still disable and enable it (until its **Client ID** or **Client Secret** changes). One that was disabled shows **Not tested** and needs a passing test, after which you enable it yourself.

For Google, use the **Google** provider rather than OpenID Connect: Dstl8 refuses `https://accounts.google.com` as an issuer URL.
{% endtab %}
{% endtabs %}

### Add the connection in Dstl8

#### 1. Create the connection

1. Go to **Organization Settings → SSO** and click **Add SSO**.
2. Fill in the **Add SSO Configuration** dialog:

| Field             | Notes                                                                                                                                                                                                                           |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Type**          | **OAuth 2.0**.                                                                                                                                                                                                                  |
| **Name**          | For OpenID Connect, shown on the sign-in button as **Continue with** *name*: use something your users recognize, such as `Contoso`. Google and GitHub buttons always read **Continue with Google** or **Continue with GitHub**. |
| **Provider**      | **OpenID Connect (Entra ID, Auth0, Okta…)**, or **Google** or **GitHub** for those apps.                                                                                                                                        |
| **Issuer URL**    | OpenID Connect only. The issuer URL from your identity provider. `https://` is already filled in: type the rest, or paste the full URL.                                                                                         |
| **Client ID**     | The client or application ID from your identity provider.                                                                                                                                                                       |
| **Client Secret** | The client secret from your identity provider.                                                                                                                                                                                  |

3. Click **Create Connection**.

Dstl8 checks the issuer URL with your identity provider before saving. The new connection appears in the list as **Not tested**: it doesn't show on the sign-in page until it passes a test.

An organization can have one connection per issuer URL.

#### 2. Test and enable

1. In the connection's actions menu, click **Test Connection**. A new tab opens your identity provider's sign-in page.
2. Sign in with your own account at the identity provider.
3. When the first test succeeds, Dstl8 marks the connection as verified and enables it. It now shows **Enabled**, and its **Continue with** button appears on your organization's sign-in page.

The test result also tells you whether your identity provider sent an email address Dstl8 trusts. If it says the email isn't trusted, users can't sign in through this connection until you fix it, so an OpenID Connect connection isn't enabled: the test counts as passed, but the connection shows **Disabled**. For Entra, add the [email claims](#id-3.-add-the-email-claims) and make sure the user has an email address; for Auth0 and Okta, make sure the user's email is verified. Then test again; a test with a trusted email enables it. You can still enable the connection by hand after an untrusted test, but every invited user's first sign-in through it is then refused until the email is fixed.

You can't enable a connection that hasn't passed a test, whatever its provider. The exception is a Google or GitHub connection that was already enabled before tests were required: it counts as tested until its **Client ID** or **Client Secret** changes (see the **Google or GitHub** tab). To pause SSO sign-ins without deleting the connection, choose **Disable** from its actions menu. Testing a connection you disabled doesn't turn it back on; choose **Enable** when you're ready.

### Edit a connection

1. Go to **Organization Settings → SSO**, and in the connection's actions menu, click **Edit**.
2. In the **Edit SSO Configuration** dialog, change the **Name**, **Client ID**, **Client Secret** or, for OpenID Connect, **Issuer URL**. Leave **Client Secret** blank to keep the current secret.
3. Click **Update Connection**.

**Type** and **Provider** can't be changed. To use another provider, add a new connection.

A new **Name** takes effect as soon as you save and doesn't need a new test. For OpenID Connect, the sign-in button shows the new name.

A new **Client ID** or **Client Secret**, or an **Issuer URL** that points at a different issuer, needs a new test, like a new connection: the client ID and issuer URL decide which app, at which identity provider, signs your users in, and the last test didn't use the new secret. The same issuer typed another way, for example without its trailing slash, isn't a new one. The dialog warns you before you save. Until **Test Connection** passes with the new settings, the connection shows **Not tested** and can't be enabled.

* **If the connection is enabled, saving turns it off**, and its **Continue with** button leaves the sign-in page. When **Test Connection** passes with the new settings, the connection turns back on. For OpenID Connect, only a test that returns a trusted email turns it back on, as with a new connection.
* **If you disabled it, it stays disabled.** A passing test doesn't turn it back on: the list then shows **Disabled** instead of **Not tested**, and you choose **Enable**. The same goes for a Google or GitHub connection that was disabled before tests were required.
* **If it was waiting for a test, the next passing test turns it on.** That's a new connection that no test has turned on yet, or one that an earlier **Client ID**, **Client Secret** or **Issuer URL** change turned off. For OpenID Connect, only a test that returns a trusted email turns it on.

If a passing test says the email isn't trusted (OpenID Connect only), a connection waiting for a test stays off and shows **Disabled**. A later test with a trusted email turns it on, or you can choose **Enable** by hand. As with a [new connection](#id-2.-test-and-enable), enabling it by hand means every invited user's first sign-in through it is refused until the email is fixed.

To switch to another app at your identity provider, set it up as for a new connection, then enter both its **Client ID** and its **Client Secret**.

After an **Issuer URL** change, users are matched again by their trusted email on their next sign-in, as on their first. So before you change it, check that each SSO user's email on the **Users** page is the one the new identity provider will send, and that it sends it as trusted. Otherwise those users see "You're not a member of this organization yet" or "did not share a verified email address" (see [Troubleshooting](#troubleshooting)).

Users stay linked after a **Client ID** change when the new app gives each user the same user ID as the old one. Google and GitHub do. Auth0 and Okta do by default for apps in one tenant or organization, and a new **Client ID** at the same **Issuer URL** stays in the same one. Entra ID does, because Dstl8 identifies users by their object ID. If your identity provider gives each app its own user IDs, linked users see "already linked to a different account" on their next sign-in: an admin uses **Unlink** on the **Users** page (a superadmin's account takes a **Superadmin**), and the user's next SSO sign-in links the new account. Check this with your identity provider before you switch apps.

#### Replacing a client secret

Replace a client secret before it expires. Users stay linked. Saving the new secret turns an enabled connection off until **Test Connection** passes with it, so SSO sign-ins through the connection pause between saving and testing. Have your identity provider's console and Dstl8 open, and test right after saving. Under **Client Secret**, the **Edit** dialog shows when the current secret was saved; older connections show it once their secret has been replaced.

If your identity provider lets an app have two secrets at once, as Entra ID, Okta, Google and GitHub do:

1. Create a new client secret at your identity provider (for Entra, see [Create a client secret](#id-2.-create-a-client-secret)). Don't delete the old one yet: sign-ins use it until you save the new one.
2. In the connection's actions menu, click **Edit**, paste the new secret into **Client Secret**, and click **Update Connection**. An enabled connection turns off.
3. Run **Test Connection** right away. A passing test turns the connection back on. For OpenID Connect, only a test that returns a trusted email turns it back on.
4. Once the test passes, delete the old secret at your identity provider.
5. Run **Test Connection** again. With the old secret gone, it can only pass with the new one. If it fails, the secret you deleted was probably the new one: create another secret at your identity provider, save it in Dstl8, and run **Test Connection**.

If your identity provider keeps one client secret per app, as Auth0 does, a new secret replaces the old one there, and SSO sign-ins through the connection fail from the moment you rotate it until **Test Connection** passes with the new one. Open the connection's **Edit** dialog first. Then rotate the secret at your identity provider, paste the new one into **Client Secret**, click **Update Connection** right away, and run **Test Connection**. Auth0 can take up to 30 seconds to switch to a new secret, so if the test fails, wait that long and test again before you rotate the secret again.

If Dstl8 can't store the new secret, the dialog asks you to enter it again. The connection is already off and needs a new test, and it keeps its old secret. Enter the new secret again, click **Update Connection**, and run **Test Connection**.

{% hint style="warning" %}
**A wrong secret keeps the connection off.** If the new secret is wrong, **Test Connection** fails and the connection stays off. Enter the right secret and test again. For Entra, paste the secret's **Value**, not its **Secret ID**.
{% endhint %}

### Invite your users

1. Go to **Organization Settings → Users** and click **Invite User**.
2. Enter the user's **Email Address**, exactly as your identity provider sends it.
3. Choose their **Org Role**, no higher than your own, and their **Workspace Assignments**, then send the invitation.

Repeat for each user. The first time they sign in through SSO, Dstl8 links their identity provider account to this invitation.

### Sign in with SSO

Share your organization's sign-in page with your users:

```
https://<org_id>.app.dstl8.ai/login
```

They click your connection's **Continue with** button, sign in at your identity provider, and land in Dstl8.

{% hint style="info" %}
**Use the organization URL, not the invitation link.** The link in the invitation email opens the general Dstl8 sign-up page, which doesn't offer your organization's SSO connection. Users who sign in with SSO should go to `https://<org_id>.app.dstl8.ai/login` instead.
{% endhint %}

### CLI and automation

The `dstl8` CLI can't sign in through SSO yet, so `dstl8 login` doesn't work for users who only have an SSO account. Use an API token instead:

* **Members with the Admin or Superadmin role** can create their own token: go to **Organization Settings → Tokens** and click **Create API Token**. Copy the token right away; it's shown only once.
* **Members with the default User role** can't create personal tokens. An admin can create a service token for them; see [Getting a token](/controltheory-documentation/dstl8-docs/code-graph/ci.md#getting-a-token).

{% hint style="warning" %}
**A service token belongs to the organization, not to the member.** It acts with the role it was created with, its actions aren't attributed to the member, and removing or demoting the member doesn't revoke it. When the member leaves or changes role, revoke the token under **Organization Settings → Tokens**.
{% endhint %}

Then point the CLI at your organization through the environment:

```bash
export DSTL8_API_TOKEN="dstl8_…"
export DSTL8_API_URL="https://<org_id>.app.dstl8.ai"   # no /api suffix
```

With both set, the CLI authenticates from the environment and needs no `dstl8 login`. Nothing is saved as a profile, so `dstl8 profiles` doesn't list it; run `dstl8 workspaces` to check that the CLI reaches your organization.

### Troubleshooting

| What you see                                                                                | What to do                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| You're not a member of this organization yet                                                | No Dstl8 user in your organization has that email address. [Invite them](#invite-your-users) with the email address your identity provider sends, then have them sign in again.                                                                                                                                                                                                                                                                                                                                                                                                                                |
| Your identity provider did not share a verified email address for your account              | Your identity provider sent no email address, or one it doesn't vouch for. For Entra, add both the `email` and `xms_edov` [optional claims](#id-3.-add-the-email-claims) and make sure the user has an email address on a domain verified in your tenant. For Auth0 and Okta, verify the user's email.                                                                                                                                                                                                                                                                                                         |
| Your user is already linked to a different account from this identity provider              | That Dstl8 user already signed in with another account at the same identity provider. An admin uses **Unlink** on the **Users** page (a superadmin's account takes a **Superadmin**); the user's next SSO sign-in links the new account.                                                                                                                                                                                                                                                                                                                                                                       |
| More than one user in this organization has this email address                              | Two users in your organization share the email address. Remove the duplicate on the **Users** page, or contact support.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| This sign-in option is no longer available                                                  | The connection was disabled or deleted after the user opened the sign-in page. Saving a new **Client ID**, **Client Secret** or **Issuer URL** also turns an enabled connection off until a test passes. An admin checks **Organization Settings → SSO**. If its **Client ID**, **Client Secret** or **Issuer URL** was just changed, run **Test Connection**: a passing test (for OpenID Connect, with a trusted email) turns it back on by itself. Otherwise, enable the connection (if it shows **Not tested**, run **Test Connection** first), or add it again. Until then, users can sign in another way. |
| `AADSTS9002327`                                                                             | The redirect URI is registered as a single-page application in Entra. Move it to the **Web** platform; see [Register an application](#id-1.-register-an-application).                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `AADSTS50105`                                                                               | **Assignment required?** is on and the user isn't assigned to the app. Assign them under **Enterprise applications → Users and groups**.                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `AADSTS50011`, or any "redirect URI mismatch" error                                         | The redirect URI registered at your identity provider doesn't match. Copy it again from **SSO Setup Info** and paste it exactly.                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| The issuer URL is refused when you create or edit the connection                            | For Entra, use `https://login.microsoftonline.com/<Directory (tenant) ID>/v2.0` with the tenant ID from the app's **Overview** page. For Google, add a connection with the **Google** provider; a saved connection's provider can't be changed. Otherwise, check the URL starts with `https://` and matches your identity provider's issuer.                                                                                                                                                                                                                                                                   |
| A connection for this issuer already exists                                                 | Each issuer URL, and each of Google and GitHub, can have one connection. [Edit](#edit-a-connection) the existing one instead, or delete it first.                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| The connection was edited while it was being tested                                         | Someone saved a new **Client ID**, **Client Secret** or **Issuer URL** while the test ran, so the test recorded nothing. Run **Test Connection** again.                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| Sign-in link expired                                                                        | The sign-in has to finish within 30 minutes, in the same browser tab it started in. Start again from your organization's sign-in page, or run **Test Connection** again.                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| **Enable** fails for a new or edited connection                                             | The connection hasn't passed a test since it was created, or since its **Client ID**, **Issuer URL** or **Client Secret** changed. Run **Test Connection**; once it passes, you can enable it.                                                                                                                                                                                                                                                                                                                                                                                                                 |
| The SSO button doesn't appear on the sign-in page                                           | Make sure you're on `https://<org_id>.app.dstl8.ai/login` and that the connection shows **Enabled** under **Organization Settings → SSO**.                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| Sign-ins stopped working after months of use, or right after you replaced the client secret | If you just replaced the client secret, the connection is off until **Test Connection** passes with the new one: run it. Otherwise, the client secret may have expired; the connection's **Edit** dialog shows when its current secret was saved. See [Replacing a client secret](#replacing-a-client-secret).                                                                                                                                                                                                                                                                                                 |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.controltheory.com/controltheory-documentation/dstl8-docs/single-sign-on.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
