# Multi-Provider SSO Integration Plan ## Overview - Deliver Google Workspace and Microsoft 365 SSO that fits the existing multi-tenant NextAuth.js stack without disrupting current credential flows. - Normalize identity data across providers so tenant routing, OTT vanity handoff, and user_type logic continue to function uniformly. - Provide a guided migration path that lets password-based users link SSO identities safely and allows ops to monitor and enforce adoption by tenant. ## Phased Technical Plan ### Phase 0 – Foundations and Provider Enablement - [ ] Provision Azure AD enterprise app, confirm Google OAuth project, and capture client IDs, secrets, tenant IDs, and redirect URIs. - [x] Update secret loader so Microsoft OAuth values (client ID, secret, authority/tenant) are available alongside Google via `getNextAuthSecret`. - [x] Add Microsoft provider configuration (e.g., `AzureADProvider`) and refresh Google provider options to share the common claim extractor. - [x] Refresh `.env.example`, devbox/dev compose templates, and developer docs to include both providers for local testing. - [x] Document OAuth secrets and redirect URIs in EE configuration guides while leaving CE `.env.example` entries absent or commented. - [x] Wrap provider registration in `server/src/app/api/auth/[...nextauth]/options.ts` with the existing `isEnterprise` guard so CE builds resolve to stubs. ### Phase 1 – Provider Integration and Claim Normalization - [x] Implement a shared profile mapper that converts Google and Microsoft payloads into the `ExtendedUser` schema. - [x] Extend `signIn` and `jwt` callbacks to apply tenant resolution from query `tenant_hint`, vanity-domain headers, or email-domain heuristics when provider data is incomplete. - [x] Invoke existing user validation (active status, tenant membership, user_type) within the OAuth callback before token issuance. - [x] Confirm `session` and `redirect` callbacks read normalized claims so OTT and redirect flows behave consistently across providers. - [x] Place provider adapters, claim mappers, and account-link helpers in `ee/server/src/lib/auth/ssoProviders.ts` with matching stubs in `server/src/empty/lib/auth/ssoProviders.ts`. ### Phase 2 – Account Linking and Migration Path - [x] Create or extend a `user_auth_accounts` table keyed by user ID and provider (google|microsoft) with provider subject IDs and metadata. - [x] Deliver an authenticated “Connect SSO” flow that revalidates password and TOTP before capturing OAuth provider details. - [x] Update credential login surfaces to detect linked providers, display SSO prompts, and steer users toward OAuth flows (local 2FA suppression to follow policy controls). - [ ] Build a batch backfill script for federated email domains and log unresolved accounts for manual review. - [x] Store schema migrations for the new linking table under `ee/server/migrations` and supply CE no-op stubs. - [x] Serve SSO buttons and the “Connect SSO” settings page from `@ee` components/pages with CE stubs in `server/src/empty`. ### Phase 3 – Built-in SSO Bulk Assignment UI - [ ] Extract bulk assignment preview/execute helpers into `ee/server/src/lib/actions/ssoActions.ts` (with CE stubs in `server/src/empty`) so the existing backfill script shares logic and only admins with `settings.update` can run it. - [ ] Build `SsoBulkAssignment.tsx` container plus `SsoBulkAssignmentForm.tsx` to render provider toggles, multi-domain input, and an internal/client selector with preview and execute buttons wired to the new actions. - [ ] Surface preview results inline (counts for linked, skipped, already linked) and trigger toast/audit events on assignment; ensure preview falls back gracefully when no domains or providers are selected. - [ ] Add a "Single Sign-On" tab to `SecuritySettingsPage.tsx` navigation, mount the bulk assignment UI, and keep Roles/Permissions/Policies unaffected. - [ ] Extend acceptance coverage to include UI-driven dry-run/assign flows and confirm CE/enterprise builds resolve the new components and actions correctly. #### Decision (2025-11-06): Provider opt-in per user - **Summary**: New staff are explicitly classified as `internal` (password/TOTP only) or assigned a specific SSO provider at creation time. Auto-detection on login is disabled unless an admin enabled at least one provider for that user. Phase 3 still delivers the bulk assignment tooling so admins can flip providers for many accounts at once. - **Reasoning**: Automatically trusting any matching email introduced takeover risk (e.g., an attacker controlling an external domain or recycled mailbox could satisfy a loose email match). Requiring admins to opt users into a provider maintains least privilege and keeps forensic/audit signals intact. - **Intended behavior**: - User creation UI/API exposes a selector: `Internal (password only)` vs `Google` vs `Microsoft` (extendable). Default remains Internal. - Login callbacks accept OAuth responses only when the user record lists that provider; otherwise the attempt is rejected and logged. - Bulk assignment preview/execute flows remain valuable to migrate cohorts (entire domains, departments) into a provider without editing each profile. - Existing auto-link toggle is scoped to future automation that _pre-assigns_ providers; it no longer blindly creates links during login without an admin decision. ### OUT OF BAND – Rollout, Monitoring, and Policy Controls - [ ] Introduce feature flags or configuration to enable SSO providers per tenant/portal for controlled rollout. - [ ] Instrument telemetry to capture provider usage, OTT handoffs, migration completions, and repeated password fallbacks. - [ ] Add policy controls allowing tenants to require SSO and to determine whether local TOTP remains after OAuth logins. - [ ] Publish operational runbooks covering break-glass password resets, tenant onboarding checklists, and SSO troubleshooting. - [ ] Update `scripts/build-enterprise.sh`, validate CE Docker builds resolve `@ee` imports to stubs, and gate new OAuth integration tests behind `process.env.EDITION === 'enterprise'`. ### Acceptance Tests - [ ] EE Connect SSO flow: verifies password + TOTP gating, sets nonce cookie, and redirects through selected provider; linking renders in Account Management afterwards. - [ ] Migration banner journey: credential user with new SSO link sees call-to-action on next password login, completes OAuth sign-in, and subsequent logins default to provider without local 2FA prompts. - [ ] MSP credential form: with linked provider returns SSO prompt, `Sign in with ` launches OAuth and lands on dashboard. - [ ] Client portal credential form: with tenant slug + linked provider displays SSO banner and respects vanity domain callback handling. - [ ] JWT/session callbacks: ensure tenant slug and contact/client data persist across logins for both Google and Microsoft profiles. - [ ] Backfill script dry-run/live modes: process sample users without creating duplicates; verify CE builds skip EE scripts/components and migration prompts respect pre-linked accounts. ## Background and Investigational Notes ### Existing Authentication Architecture - **Tech stack**: NextAuth.js with JWT strategy (configurable `SESSION_MAX_AGE`), custom session cookies, on-demand user validation per request. - **Providers in place**: Google OAuth using `GoogleProvider`; Keycloak integration via `KeycloakProvider`; custom credentials provider with password + 2FA. - **User portals**: Internal MSP staff sign in at `/auth/msp/signin` (`user_type: internal`); client users sign in at `/auth/client-portal/signin` (`user_type: client`, with `clientId` and `contactId` requirements). - **Core files**: NextAuth handler (`server/src/app/api/auth/[...nextauth]/route.ts`), options (`.../options.ts`), credential logic (`server/src/lib/actions/auth.tsx`), registration/reset, session cookies, and portal-specific forms. - **JWT/session callbacks**: `signIn` tracks last login and client redirects; `jwt` populates claims (id, email, tenant, user_type); `session` turns tokens into session objects; `redirect` routes users by `user_type`. - **Client portal handoff**: Vanity domain redirect uses OTT tokens via `computeVanityRedirect` and `PortalSessionHandoff.tsx`. ### Credentials Flow Snapshot - Email/password checked against database hashed password. - 2FA enforced when `two_factor_enabled` using TOTP codes (passed as `twoFactorCode`). - JWT issued with tenant and user metadata; session callback mirrors data; redirect logic handles portal selection. ### OAuth Flow Snapshot - User triggers provider button; profile callback runs. - **Google**: Currently requires existing DB user by email and verifies active status; assigns default `user_type` (internal). - **Keycloak**: Accepts profile data with tenant/user_type claims. - Once profile is accepted, standard JWT/session callbacks run. ### Investigation Takeaways Relevant to Plan - Need consistent claim normalization so OTT and redirect logic remain unchanged across providers. - Tenant determination for OAuth logins is currently limited; must combine query hints, vanity headers, or email-domain mapping. - 2FA bypass expectations differ by provider; policy controls will decide whether to trust external 2FA or enforce local TOTP post-login. - Account linking is required to prevent duplicate user records and to let existing credential users migrate smoothly. - Future enhancements may include auto-provisioning (SCIM/Azure AD) and Google auto-provisioning; plan leaves hooks for these but focuses on core SSO enablement. ### SSO Migration Tooling - Use `pnpm tsx ee/scripts/backfill-sso-links.ts --provider= --domain=example.com[,domain2.com] [--dry-run]` to seed `user_auth_accounts` rows for federated domains. - The script skips inactive users, records metadata indicating the backfill source, and treats email addresses as provisional provider IDs until real OAuth logins occur. - Run in `--dry-run` mode first to review counts, then execute without the flag to persist links; reruns are idempotent and update only matching tenant/user combinations.