Skip to content

feat(sso): several identity providers per organization - #7652

Merged
waleedlatif1 merged 8 commits into
stagingfrom
feat/multi-idp
Sep 9, 2026
Merged

feat(sso): several identity providers per organization#7652
waleedlatif1 merged 8 commits into
stagingfrom
feat/multi-idp

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • An organization can run several identity providers at once, one per verified domain (e.g. Okta for eng.acme.com, Entra ID for acme.com)
  • Sign-in resolves the provider for the email domain server-side (POST /api/auth/sso/resolve, verified domains only) and passes providerId to Better Auth instead of relying on its unordered domain lookup
  • One provider per domain is enforced at the database: migration 0333_sso_provider_org_domain_unique adds a partial unique index on sso_provider (organization_id, normalized domain), built concurrently behind a duplicate pre-check (production has none). Registration checks first for a clear 409 (SSO_DOMAIN_ALREADY_ROUTED) and maps a lost race to the same response
  • New DELETE /api/auth/sso/providers/[providerId] (Sim-owned; the plugin's delete stays blocked). Org owners/admins delete org providers, creators delete personal ones. Accounts and memberships are untouched
  • Settings sign-in tab lists providers (ordered), opens one via ?provider=, adds another, deletes with confirmation; an org with none goes straight to the form
  • JIT admissions record providerId in audit metadata
  • Docs: multiple providers, domain routing, deletion

Type of Change

  • New feature

Testing

  • Full app suite, bun run check:audits, check:migrations origin/staging, type-check, docs-manifest:check, check:openapi
  • New tests: resolve route, delete route, registration guard, sign-in form provider naming and 404-only "no provider" message, settings list/add/detail/delete
  • Better Auth SSO plugin traced end to end for the explicit-providerId path (sign-in, OIDC and SAML callbacks, catch-all deny list, in-flight sign-ins after delete); no live IdP run

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

An organization can now run more than one identity provider, one per
verified domain: Okta for eng.acme.com and Entra for acme.com, for example.
The schema already allowed several rows; the product could not show, pick,
or remove them.

- Sign-in names the provider explicitly. A new POST /api/auth/sso/resolve
  maps an email domain to the provider that serves it, preferring a verified
  domain and breaking ties on provider id, so routing no longer depends on
  the SSO plugin's unordered domain lookup.
- Registration refuses a second provider on a domain the caller already
  routes (SSO_DOMAIN_ALREADY_ROUTED), keeping email-domain routing
  deterministic. The check shares the existing cross-tenant domain read.
- DELETE /api/auth/sso/providers/[providerId], owned by Sim since the
  plugin's delete-provider stays blocked: organization owners and admins
  remove an organization provider, the creator a personal one. Accounts and
  memberships the provider admitted are untouched.
- The provider list is ordered by provider id; the settings sign-in tab
  lists providers, opens one by URL (`provider=`), adds another, and
  deletes with confirmation. JIT admissions record the provider id in
  their audit metadata.
- Docs describe multiple providers, domain routing, and deletion.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CkZjSKgtFdE5mzsQ31gGLY
@vercel

vercel Bot commented Sep 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Sep 9, 2026 6:17pm UTC

Request Review

@greptile-apps

greptile-apps Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 2/5

The PR is not yet safe to merge because provider routing can select unverified claims, concurrent registration can create ambiguous domain routes, and some authorized self-hosted administrators cannot access the new management UI.

Findings

  1. P1 Unverified providers are resolved
  2. P1 Domain routing check races
  3. P1 Authorized admins lose controls
  4. P2 Route bypasses operation boundary

Summary

  • Introduces a public email-domain resolver and passes its selected provider explicitly into SSO sign-in.
  • Adds organization-aware provider deletion and multi-provider settings UI.
  • Rejects a second provider for an already-routed domain during registration.
  • Documents multi-provider routing and deletion behavior.
  • The current implementation still routes through unverified providers, does not atomically enforce domain uniqueness, and hides management controls from some self-hosted organization administrators.

Diagram

sequenceDiagram
  actor User
  participant UI as SSO sign-in form
  participant Resolver as POST /api/auth/sso/resolve
  participant DB as sso_provider
  participant Auth as Better Auth SSO
  participant IdP as Selected identity provider
  participant Admission as SSO admission use case

  User->>UI: Enter work email
  UI->>Resolver: Resolve email domain
  Resolver->>DB: Find domain claim
  DB-->>Resolver: providerId
  Resolver-->>UI: providerId and protocol
  UI->>Auth: signIn.sso(email, providerId)
  Auth->>IdP: Redirect for authentication
  IdP-->>Auth: SSO callback
  Auth->>Admission: Admit user for providerId
  Admission->>DB: Verify provider trust and organization
  Admission-->>Auth: Admit or reject
Loading

Comment thread apps/sim/app/api/auth/sso/resolve/route.ts Outdated
Comment thread apps/sim/app/api/auth/sso/register/route.ts Outdated
Comment thread apps/sim/ee/sso/components/sso-settings.tsx
Comment thread apps/sim/app/api/auth/sso/providers/[providerId]/route.ts
…ettings

- Registration guard uses the same domain expression as sign-in resolution and domain verification
- Self-host registration script stores the normalized provider domain
- Sign-in reports "no provider" only on 404; other failures keep the generic message
- Settings: canonical back slot and title on provider detail and form, Edit then Delete, no duplicate Open in the row menu, awaited list refetch before navigating, replace-history on close, unsaved-changes guard on back
- Docs: state that an organization can use more than one identity provider
…ve only verified providers

- Partial unique index on sso_provider (organization_id, normalized domain), built concurrently after a duplicate pre-check; a lost race maps to the same 409 as the pre-check
- Sign-in resolution returns only providers whose domain is verified
- Settings controls follow the organization owner/admin model the server enforces, not the creator
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
waleedlatif1 merged commit 4d5e4aa into staging Sep 9, 2026
31 checks passed
@waleedlatif1
waleedlatif1 deleted the feat/multi-idp branch September 9, 2026 18:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant