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

# Sign-in and SSO

> Auth modes, OIDC and Entra ID, Google, SAML 2.0, and LDAP / Active Directory

This page is about how people sign in **to Bag of words**. For how a connection authenticates to a *data source* — shared credentials versus per-user sign-in — see [Authentication and Access](/data-sources/authentication).

## Auth modes

One setting decides which sign-in methods the login page offers:

| `auth.mode` | Password form | SSO buttons |
| - | - | - |
| `hybrid` | yes | yes |
| `local_only` | yes | no |
| `sso_only` | no | yes |

```yaml theme={null}
auth:
  mode: "hybrid"   # local_only | sso_only | hybrid
```

<Note>
  **`?local=true` is the escape hatch.** On an `sso_only` deployment, visiting `/users/sign-in?local=true` shows the password form anyway. Keep one local administrator who can use it — otherwise a misconfigured or unreachable identity provider locks everyone out, including you.
</Note>

### Single-provider auto-start

When the mode is `sso_only` **and exactly one provider is enabled**, there is nothing for the user to choose, so BOW starts the round trip for them: the login page redirects straight to the provider instead of rendering a single button.

The redirect is suppressed when `?local=true` is set, or when the provider bounced back with an error — so a failed sign-in can always show you why, rather than looping.

An embedding application that already knows whose session it is opening can pass `login_hint`, which is forwarded to the provider so a browser holding several accounts doesn't stop on a chooser:

```text theme={null}
/users/sign-in?login_hint=person@company.com
```

## OIDC providers

Any OIDC-compliant provider works. Entra ID (Azure AD) and Okta are the common ones.

```yaml theme={null}
oidc_providers:
  - name: entra
    enabled: true
    issuer: https://login.microsoftonline.com/<tenant-id>/v2.0
    client_id: <application-client-id>
    client_secret: ${BOW_ENTRA_CLIENT_SECRET}
    scopes: ["openid", "profile", "email"]
    pkce: true
    client_auth_method: post
    discovery: true
    uid_claim: sub
```

The `name` is the routing slug, and it determines the callback path — **register this exact URL as a redirect URI on the provider**, or sign-in fails before the login form appears:

```text theme={null}
https://<your-bow-host>/api/auth/<name>/callback
```

The sign-in button shows the provider's real name and logo — "Continue with Microsoft", not "entra". That is derived from the `issuer` URL, not from `name`, so a provider called `corp-sso` pointing at `login.microsoftonline.com` still renders as Microsoft. Issuers that aren't recognised get a neutral lock icon and their configured label.

<Note>
  Put `client_secret` in an environment variable or a Kubernetes Secret and reference it with `${VAR}`. Never commit it to `bow-config.yaml`.
</Note>

### Group sync

Set `sync_groups: true` with a `group_claim` to map provider groups onto BOW roles. `resolve_group_names: true` turns group object IDs into readable names where the provider supports it.

## Google

```yaml theme={null}
google_oauth:
  enabled: true
  client_id: ${BOW_GOOGLE_CLIENT_ID}
  client_secret: ${BOW_GOOGLE_CLIENT_SECRET}
```

Google appears alongside the OIDC providers in the same button list and counts toward the single-provider auto-start rule above.

## SAML 2.0

Any SAML 2.0 identity provider works — Entra ID, Okta, PingFederate, ADFS, or anything else that can be configured to this profile. Like OIDC providers, SAML providers appear as buttons on the login page and count toward the single-provider auto-start rule above.

```yaml theme={null}
base_url: https://bow.company.com
saml_providers:
  - name: company-sso
    enabled: true
    label: Company SSO
    organization_id: <existing-bow-organization-id>
    idp:
      metadata_url: https://idp.company.com/app/metadata
    attributes:
      subject: name_id
      email: mail
      name: displayName
    auto_provision_users: false
```

**`base_url` must be an HTTPS origin.** An enabled SAML provider is rejected at startup if `base_url` is not `https://` or carries a path. The browser posts the assertion to BOW cross-site, which needs a `Secure` cookie.

**Each provider admits exactly one organization.** `organization_id` names an organization that already exists. A valid assertion never admits a user anywhere else.

### Register BOW with your identity provider

For a provider named `company-sso`, give your IdP these values. The `name` must be a lowercase slug, unique across all SAML, OIDC and Google providers.

| Setting | Value |
| - | - |
| SP metadata | `https://<your-bow-host>/api/auth/saml/company-sso/metadata` |
| Entity ID / Identifier / Audience | `https://<your-bow-host>/saml/company-sso` |
| ACS / Reply URL | `https://<your-bow-host>/api/auth/saml/company-sso/acs` |
| Sign-on URL | `https://<your-bow-host>/users/sign-in` |

Scheme, host, port and path must match exactly. Leave RelayState and the logout URL unset, and assign the users who should have access to the IdP application.

### Identity provider trust

Pick one source of trust under `idp`:

<Tabs>
  <Tab title="Metadata URL">
    ```yaml theme={null}
    idp:
      metadata_url: https://idp.company.com/app/metadata
      entity_id: https://idp.company.com/issuer   # only if the metadata lists several IdPs
    ```

    Fetched over HTTPS without following redirects, cached for five minutes, and metadata expiry is honoured.
  </Tab>

  <Tab title="Metadata file">
    ```yaml theme={null}
    idp:
      metadata_file: /etc/bow/company-idp.xml
    ```

    Use this for air-gapped deployments or when the IdP only offers a download.
  </Tab>

  <Tab title="Explicit certificates">
    ```yaml theme={null}
    idp:
      entity_id: https://idp.company.com/issuer
      sso_url: https://idp.company.com/sso
      certificates:
        - ${BOW_SAML_IDP_CERTIFICATE}
        - ${BOW_SAML_IDP_NEXT_CERTIFICATE}
    ```

    Certificates are the full public X.509 signing certificates, not fingerprints. To rotate, trust the old and new certificate together, then remove the old one.
  </Tab>
</Tabs>

BOW never trusts a certificate just because an incoming assertion carries it, and SHA-1 signatures are rejected.

### Attribute mapping

`attributes` maps assertion attributes to the BOW user. Use the attribute's exact **Name** — including the full URI if your IdP emits one — not its friendly or display name.

| Key | Default | Notes |
| - | - | - |
| `subject` | `name_id` | The stable account identifier. `name_id` uses the NameID; otherwise name an immutable attribute such as an object ID. Transient NameIDs are rejected. |
| `email` | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress` | Must hold exactly one valid email address. |
| `name` | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name` | Optional. Falls back to the part of the email before `@`. |

### Admission and existing accounts

With `auto_provision_users: false`, a person signing in for the first time needs a pending invitation (from the **Members** tab in settings) for their email in that organization, and takes the role the invitation assigns. With `true`, anyone your IdP lets through joins the organization as a member; seat limits still apply.

**A SAML login never takes over an existing account just because the email matches.** To connect an existing member to their SAML identity, approve the link explicitly:

```yaml theme={null}
account_links:
  "<immutable-idp-subject>": person@company.com
```

The key is the NameID (or the mapped `subject` attribute). The assertion must carry the same email, and the account must already be a member of the organization. Superusers, service accounts and LDAP-bound users can't be linked this way.

Removing someone from the organization, deactivating them, or disabling the provider ends their SAML sessions, and auto-provisioning will not re-admit a member you removed.

### Signed requests and encrypted assertions

Both are optional and need a service provider key pair:

```yaml theme={null}
sp:
  certificate_file: /run/secrets/saml-sp.crt
  private_key_file: /run/secrets/saml-sp.key
  sign_requests: true
  want_assertions_encrypted: true
```

Import BOW's SP metadata into your IdP after adding the key pair. Neither key material nor IdP metadata is exposed through BOW's public settings.

### What SAML does not cover

* **Start sign-in from BOW.** IdP-initiated (unsolicited) logins are not accepted; an IdP portal tile should link to `/users/sign-in`.
* **Signing out of BOW ends the BOW session only**, not the IdP session. SAML Single Logout is not supported.
* **Roles don't come from SAML claims.** Use invitations, SCIM, LDAP group sync, or assign roles in BOW.
* **No delegated data access.** A SAML assertion is not an OAuth token, so per-user data connections still need their own sign-in — see [Authentication and Access](/data-sources/authentication).

### Troubleshooting

| Symptom | Check |
| - | - |
| No SAML button on the login page | `auth.mode` is not `local_only`, the provider is `enabled`, and BOW was restarted after editing the config. |
| "Configuration unavailable" | The metadata URL or file is reachable and unexpired, and `entity_id` selects the right IdP. |
| Sign-in fails after the IdP | Start again from BOW — requests expire after five minutes. Make sure BOW is served over trusted HTTPS. |
| "Missing identity attribute" | The attribute **Name** matches exactly, and the user's email field is populated in the IdP. |
| Existing user rejected | Add an `account_links` entry for them, or invite them fresh. |

## LDAP and Active Directory

<Note>
  LDAP requires an **Enterprise license**. See [License Key](/enterprise/license).
</Note>

LDAP authenticates users against your directory and can keep group membership in sync. It is designed to **fail closed** — every condition below is required, and none of them can be turned off.

```yaml theme={null}
ldap:
  enabled: true
  url: ldaps://ad.company.com:636
  bind_dn: cn=service-account,ou=Services,dc=company,dc=com
  bind_password: ${BOW_LDAP_BIND_PASSWORD}
  ca_certs_file: /run/bow/directory-ca.pem
  organization_id: <existing-bow-organization-id>
  admission_group_dn: cn=BOW Users,ou=Groups,dc=company,dc=com
  kerberos_realm: COMPANY.COM
  auto_provision_users: true
  allow_local_superuser_login: false
  base_dn: dc=company,dc=com
```

**A service account is required.** `bind_dn` and `bind_password` must be set — anonymous bind is not accepted. Give the account read-only lookup rights and nothing more.

**TLS is always verified.** Certificate checking cannot be disabled. Use `ldaps://` on 636, or `start_tls: true` on 389 — the connection is verified before either bind is attempted. If your directory presents a private CA, point `ca_certs_file` at the mounted bundle; leave it empty to use the system trust store.

**Admission is explicit.** A user may sign in only if they are a member of `admission_group_dn`, and each deployment admits exactly one BOW organization via `organization_id`. Nested AD group membership is resolved by AD itself, so nested groups work as your directory defines them.

**An LDAP login cannot take over a local account** that happens to share the same email address; the collision is rejected rather than merged.

**There is no password fallback during an outage.** If the directory is unreachable, directory users cannot sign in — BOW will not silently fall back to a local password. `allow_local_superuser_login: true` is the one deliberate recovery path, and it applies only to local superusers that are not linked to a directory identity.

### Group sync

Sync is scoped to the organization named by `organization_id` and only touches memberships it owns. An empty or partial directory response will not delete invited members, and a reconciliation that fails partway rolls back rather than applying half of itself.

### Delegated SQL identity

When a SQL Server connection uses per-user Kerberos delegation, the impersonated principal comes **only from a verified directory identity** — the provider, the account's SID and its subject must all match. It is never inferred from the signed-in user's email address.

`kerberos_realm` sets the AD realm explicitly, so it stays correct when your users' email or UPN suffixes differ from the realm name. See [SQL Server](/data-sources/connectors/sql-server) for the delegation setup itself.

## Sessions

Session tokens are revocable. Signing out, changing or resetting a password, and an administrator's force-signout all invalidate every token already issued to that user.

SSO does not return the session token in the redirect URL. The callback returns a single-use code, valid for 60 seconds, which the application exchanges for the token over POST — so the token never lands in a browser history, a proxy log, or a `Referer` header.

## Related

<CardGroup cols={2}>
  <Card title="Data source authentication" icon="lock" href="/data-sources/authentication">
    Shared credentials versus per-user sign-in, for connections rather than logins.
  </Card>

  <Card title="Deployment Options" icon="server" href="/install">
    Where `bow-config.yaml` lives, and the Helm values that mirror it.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.