---
title: SSO Providers
type: runbook
audience: admin
status: current
source_of_truth: false
---

# SSO Providers

Configure optional enterprise single sign-on for Mosaic Stack through Better Auth's generic OAuth integration. The gateway owns provider configuration and discovery; the web application renders only providers reported by the gateway.

> **Current behavior:** Authentik, WorkOS, and Keycloak are supported in the checked-in implementation. Authentik and WorkOS use OIDC. Keycloak supports OIDC and an optional direct SAML login URL. The web application does **not** read `NEXT_PUBLIC_WORKOS_ENABLED` or `NEXT_PUBLIC_KEYCLOAK_ENABLED`; provider buttons are discovered dynamically from the gateway.

## Prerequisites

Before configuring a provider, establish these gateway settings:

- `BETTER_AUTH_URL` — the public base URL used to construct OAuth callback URLs.
- `BETTER_AUTH_SECRET` — a strong secret for Better Auth sessions and tokens.
- `GATEWAY_CORS_ORIGIN` — the web origin or comma-separated origins allowed by the gateway.

Keep client secrets and Better Auth secrets in the deployment secret store. Do not commit them to `.env` files or expose them to the web bundle.

## Provider configuration

### Authentik OIDC

Set all three variables to enable Authentik:

```bash
AUTHENTIK_ISSUER=https://auth.example.com/application/o/mosaic
AUTHENTIK_CLIENT_ID=...
AUTHENTIK_CLIENT_SECRET=...
```

The implementation derives OIDC discovery and endpoint URLs from `AUTHENTIK_ISSUER`. An optional team-claim label can be exposed in provider discovery:

```bash
AUTHENTIK_TEAM_SYNC_CLAIM=groups
```

The default reported claim is `groups`. The discovery payload reports this claim; verify any downstream membership-sync behavior separately before treating it as an authorization guarantee.

Register this redirect URI with the Authentik application:

```text
{BETTER_AUTH_URL}/api/auth/oauth2/callback/authentik
```

### WorkOS OIDC

Set all three variables to enable WorkOS:

```bash
WORKOS_ISSUER=https://your-company.authkit.app
WORKOS_CLIENT_ID=client_...
WORKOS_CLIENT_SECRET=...
```

Use the WorkOS AuthKit issuer or custom authentication domain, not a raw WorkOS REST API hostname. Mosaic derives the OIDC discovery URL by appending `/.well-known/openid-configuration` to the issuer. WorkOS uses PKCE and issuer validation in the current auth configuration.

An optional team-claim label can be exposed in provider discovery:

```bash
WORKOS_TEAM_SYNC_CLAIM=organization_id
```

The default reported claim is `organization_id`. The discovery payload reports this claim; verify any downstream membership-sync behavior separately before treating it as an authorization guarantee.

Register this redirect URI with the WorkOS application:

```text
{BETTER_AUTH_URL}/api/auth/oauth2/callback/workos
```

### Keycloak OIDC

Use either an explicit issuer or the URL-plus-realm form. The client ID and secret are required in both forms.

Explicit issuer:

```bash
KEYCLOAK_ISSUER=https://auth.example.com/realms/mosaic
KEYCLOAK_CLIENT_ID=mosaic
KEYCLOAK_CLIENT_SECRET=...
```

Derived issuer:

```bash
KEYCLOAK_URL=https://auth.example.com
KEYCLOAK_REALM=mosaic
KEYCLOAK_CLIENT_ID=mosaic
KEYCLOAK_CLIENT_SECRET=...
```

`KEYCLOAK_ISSUER` takes precedence when both forms are present. Keycloak uses PKCE and issuer validation in the current auth configuration.

An optional team-claim label can be exposed in provider discovery:

```bash
KEYCLOAK_TEAM_SYNC_CLAIM=groups
```

The default reported claim is `groups`. The discovery payload reports this claim; verify any downstream membership-sync behavior separately before treating it as an authorization guarantee.

Register this redirect URI with the Keycloak client:

```text
{BETTER_AUTH_URL}/api/auth/oauth2/callback/keycloak
```

### Keycloak direct SAML fallback

The current web flow supports a direct SAML link for Keycloak when `KEYCLOAK_SAML_LOGIN_URL` is configured:

```bash
KEYCLOAK_SAML_LOGIN_URL=https://auth.example.com/realms/mosaic/protocol/saml
```

This creates a configured Keycloak provider with `loginMode: saml`. The web login button links directly to the supplied URL as `Continue with Keycloak (SAML)`; there is no Better Auth OIDC callback for this mode. The URL must be the provider's valid SAML launch URL for the deployment.

A SAML-only Keycloak configuration does not require the Keycloak OIDC client variables. Do not combine an incomplete OIDC variable set with SAML-only configuration: partial OIDC configuration is rejected during auth setup.

## Callback and discovery contract

Better Auth is mounted at `/api/auth`. OIDC callbacks use:

```text
{BETTER_AUTH_URL}/api/auth/oauth2/callback/{providerId}
```

The gateway exposes provider discovery at:

```text
GET /api/sso/providers
```

The response includes `authentik`, `workos`, and `keycloak` records with:

- `configured` — whether a usable OIDC or Keycloak SAML configuration is present.
- `protocols` — supported protocols for the provider record.
- `loginMode` — `oidc`, `saml`, or `null`.
- `callbackPath` — the OIDC callback path, or `null` for SAML-only mode.
- `teamSync` — the configured/default claim label exposed to the UI.
- `samlFallback` — whether a direct Keycloak SAML URL is configured.
- `warnings` — partial OIDC configuration warnings when reported.

The web login page filters this response to configured providers. OIDC buttons use Better Auth's `signIn.oauth2` flow. A configured Keycloak SAML fallback is rendered as a direct link. No provider-specific `NEXT_PUBLIC_*_ENABLED` flag is required or consumed.

## Configuration procedure

1. Set `BETTER_AUTH_URL` to the public gateway URL that the identity provider can reach.
2. Set `BETTER_AUTH_SECRET` and the correct `GATEWAY_CORS_ORIGIN` values.
3. Choose one provider configuration above and set its complete required variable group.
4. Register the exact OIDC callback URI with the identity provider, when using OIDC.
5. Restart or redeploy the gateway so it loads the changed environment.
6. Inspect discovery without exposing secrets:

   ```bash
   curl "$BETTER_AUTH_URL/api/sso/providers"
   ```

7. Open the web login page and confirm that only configured providers are shown.
8. Complete a sign-in and verify the callback returns to the configured application.

## Partial configuration and failure modes

Provider configuration is optional. If no provider variables are set, the gateway can run without SSO providers and the web login page renders no SSO section.

For Authentik and WorkOS, setting only part of the issuer/client ID/client secret group raises a configuration error. For Keycloak OIDC, the client ID, client secret, and either an explicit issuer or a complete URL-plus-realm pair are required. Empty or whitespace-only values are treated as unset.

Common failures:

| Symptom                                               | Check                                                                                                                |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Provider does not appear on the login page            | Query `/api/sso/providers`; verify the complete provider variable group is present in the gateway environment.       |
| OAuth callback is rejected                            | Compare the registered redirect URI character-for-character with `BETTER_AUTH_URL` and the provider callback path.   |
| Provider is shown but sign-in cannot start            | Check `loginMode`, issuer discovery, client credentials, and gateway logs.                                           |
| Keycloak SAML button is absent                        | Set `KEYCLOAK_SAML_LOGIN_URL` to the provider's direct launch URL and reload the gateway.                            |
| Startup/auth initialization reports missing variables | Remove the partial provider configuration or provide the complete required group.                                    |
| SSO succeeds but team membership is unexpected        | Treat `teamSync.claim` as discovery metadata and verify the actual claim mapping and membership-sync implementation. |

Do not enable a provider by adding the obsolete `NEXT_PUBLIC_WORKOS_ENABLED` or `NEXT_PUBLIC_KEYCLOAK_ENABLED` variables. They are not read by the current web application.

## Related

- [Administrator guide](../README.md)
- [Security chapter](README.md)
- [API documentation index](../../API/README.md)
- [Documentation atlas](../../README.md)
