Files
stack/docs/SSO-PROVIDERS.md
Jason Woltje 77ba13b41b
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
feat(auth): add WorkOS and Keycloak SSO providers
2026-03-19 20:30:00 -05:00

3.4 KiB

SSO Providers

Mosaic Stack supports optional enterprise single sign-on through Better Auth's generic OAuth flow. The gateway mounts Better Auth under /api/auth, so every provider callback terminates at:

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

For the providers in this document:

  • Authentik: {BETTER_AUTH_URL}/api/auth/oauth2/callback/authentik
  • WorkOS: {BETTER_AUTH_URL}/api/auth/oauth2/callback/workos
  • Keycloak: {BETTER_AUTH_URL}/api/auth/oauth2/callback/keycloak

Required environment variables

Authentik

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

WorkOS

WORKOS_ISSUER=https://your-company.authkit.app
WORKOS_CLIENT_ID=client_...
WORKOS_CLIENT_SECRET=...
NEXT_PUBLIC_WORKOS_ENABLED=true

WORKOS_ISSUER should be the WorkOS AuthKit issuer or custom auth domain, not the raw REST API hostname. Mosaic derives the OIDC discovery URL from that issuer.

Keycloak

KEYCLOAK_ISSUER=https://auth.example.com/realms/master
KEYCLOAK_CLIENT_ID=mosaic
KEYCLOAK_CLIENT_SECRET=...
NEXT_PUBLIC_KEYCLOAK_ENABLED=true

If you prefer, you can keep the issuer split as:

KEYCLOAK_URL=https://auth.example.com
KEYCLOAK_REALM=master

The auth package will derive KEYCLOAK_ISSUER from those two values.

WorkOS setup

  1. In WorkOS, create or select the application that will back Mosaic login.
  2. Configure an AuthKit domain or custom authentication domain for the application.
  3. Add the redirect URI:
{BETTER_AUTH_URL}/api/auth/oauth2/callback/workos
  1. Copy the application's client_id and client_secret into WORKOS_CLIENT_ID and WORKOS_CLIENT_SECRET.
  2. Set WORKOS_ISSUER to the AuthKit domain from step 2.
  3. Create the WorkOS organization and attach the enterprise SSO connection you want Mosaic to use.
  4. Set NEXT_PUBLIC_WORKOS_ENABLED=true in the web deployment so the login button is rendered.

Keycloak setup

  1. Start from an existing Keycloak realm or create a dedicated realm for Mosaic.
  2. Create a confidential OIDC client named mosaic or your preferred client ID.
  3. Set the valid redirect URI to:
{BETTER_AUTH_URL}/api/auth/oauth2/callback/keycloak
  1. Set the web origin to the public Mosaic web URL.
  2. Copy the client secret into KEYCLOAK_CLIENT_SECRET.
  3. Set either KEYCLOAK_ISSUER directly or KEYCLOAK_URL + KEYCLOAK_REALM.
  4. Set NEXT_PUBLIC_KEYCLOAK_ENABLED=true in the web deployment so the login button is rendered.

Local Keycloak smoke test

If you want to test locally with Docker:

docker run --rm --name mosaic-keycloak \
  -p 8080:8080 \
  -e KEYCLOAK_ADMIN=admin \
  -e KEYCLOAK_ADMIN_PASSWORD=admin \
  quay.io/keycloak/keycloak:26.1 start-dev

Then configure:

KEYCLOAK_ISSUER=http://localhost:8080/realms/master
KEYCLOAK_CLIENT_ID=mosaic
KEYCLOAK_CLIENT_SECRET=...
NEXT_PUBLIC_KEYCLOAK_ENABLED=true

Web flow

The web login page renders provider buttons from NEXT_PUBLIC_*_ENABLED flags. Each button links to /auth/provider/{providerId}, and that page initiates Better Auth's signIn.oauth2 flow before handing off to the provider.

Failure mode

Provider config is optional, but partial config is rejected at startup. If any provider-specific env var is present without the full required set, @mosaic/auth throws a bootstrap error with the missing keys instead of silently registering a broken provider.