# P8-001 — WorkOS + Keycloak SSO Providers

**Branch:** feat/p8-001-sso-providers
**Started:** 2026-03-18
**Mode:** Delivery

## Objective

Add WorkOS and Keycloak as optional SSO providers to the BetterAuth configuration, following the existing Authentik pattern.

## Scope

| Surface                                  | Change                                                                  |
| ---------------------------------------- | ----------------------------------------------------------------------- |
| `packages/auth/src/auth.ts`              | Refactor provider array, add WorkOS + Keycloak conditional registration |
| `apps/web/src/lib/auth-client.ts`        | Add `genericOAuthClient()` plugin                                       |
| `apps/web/src/app/(auth)/login/page.tsx` | WorkOS + Keycloak SSO buttons gated by `NEXT_PUBLIC_*` env vars         |
| `.env.example`                           | Document WorkOS + Keycloak env vars                                     |
| `packages/auth/src/auth.test.ts`         | Unit tests verifying env-var gating                                     |

## Plan

1. ✅ Refactor `createAuth` to build `oauthProviders[]` conditionally
2. ✅ Add WorkOS provider (explicit URLs, no discovery)
3. ✅ Add Keycloak provider (discoveryUrl pattern)
4. ✅ Add `genericOAuthClient()` to auth-client.ts
5. ✅ Add SSO buttons to login page gated by `NEXT_PUBLIC_WORKOS_ENABLED` / `NEXT_PUBLIC_KEYCLOAK_ENABLED`
6. ✅ Update `.env.example`
7. ⏳ Write `auth.test.ts` with env-var gating tests
8. ⏳ Quality gates: typecheck + lint + format:check + test
9. ⏳ Commit + push + PR

## Decisions

- **WorkOS**: Uses explicit `authorizationUrl`, `tokenUrl`, `userInfoUrl` (no discovery endpoint available)
- **Keycloak**: Uses `discoveryUrl` pattern (`{URL}/realms/{REALM}/.well-known/openid-configuration`)
- **UI gating**: Login page uses `NEXT_PUBLIC_WORKOS_ENABLED` / `NEXT_PUBLIC_KEYCLOAK_ENABLED` feature flags (safer than exposing secret env var names client-side)
- **Refactor**: Authentik moved into same `oauthProviders[]` array pattern — cleaner, more extensible
- **Feature flag design**: `NEXT_PUBLIC_*` flags are opt-in alongside credentials (prevents accidental button render when creds not set)

## Assumptions

- `ASSUMPTION:` WorkOS OIDC discovery URL is not publicly documented; using direct URL pattern from WorkOS SSO docs.
- `ASSUMPTION:` `NEXT_PUBLIC_WORKOS_ENABLED=true` must be explicitly set — this is intentional (credential presence alone doesn't enable the button since NEXT_PUBLIC vars are baked at build time).

## Tests

- `auth.test.ts`: Mocks betterAuth stack, verifies WorkOS included/excluded based on env var
- `auth.test.ts`: Verifies Keycloak discoveryUrl constructed correctly

## Quality Gate Results

| Gate                     | Status                                       |
| ------------------------ | -------------------------------------------- |
| typecheck                | ✅ 32/32 cached green                        |
| lint                     | ✅ 18/18 cached green                        |
| format:check             | ✅ All matched files use Prettier code style |
| test (@mosaicstack/auth) | ✅ 8/8 tests passed                          |

## Verification Evidence

- `pnpm typecheck` — FULL TURBO, 32 tasks successful
- `pnpm lint` — FULL TURBO, 18 tasks successful
- `pnpm format:check` — All matched files use Prettier code style!
- `pnpm --filter=@mosaicstack/auth test` — 8 tests passed, 0 failed
