[PR #9037] feat: implement SSO plugin organization isolation and security hardening #16638

Open
opened 2026-04-13 10:37:25 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/9037
Author: @surendarcs
Created: 4/8/2026
Status: 🔄 Open

Base: nextHead: fix/sso-org-isolation-9013


📝 Commits (10+)

  • 60123da docs: add missing secret to getSignedCookie and remove misleading comments (#9008)
  • 3baf029 chore(docs): add remark-frontmatter (#9015)
  • 374985e docs: update community adapters and plugins pages (#9014)
  • 3b95d70 docs: change community plugin name (#8961)
  • 47dc887 docs: add @delmaredigital/payload-better-auth to community plugins (#8375)
  • f61ad1c fix: use INVALID_PASSWORD for all checkPassword failures (#8902)
  • 465c1dc ci: add one-click changeset commit and unify bot identity (#9019)
  • 2e537df fix: endpoint instrumentation to always use route template (#9023)
  • 5e7fd61 docs: hide fumadocs toc scroll indicator dot (#9021)
  • a7e359d docs: add expo sentinel docs (#9025)

📊 Changes

66 files changed (+3037 additions, -1190 deletions)

View changed files

.changeset/fresh-news-doubt.md (+5 -0)
.changeset/pr-8902.md (+5 -0)
.changeset/sso-org-isolation.md (+6 -0)
📝 .github/workflows/auto-changeset.yml (+186 -2)
📝 .github/workflows/auto-label.yml (+9 -0)
📝 .github/workflows/auto-retarget.yml (+13 -4)
📝 .github/workflows/lock-threads.yml (+10 -0)
📝 .github/workflows/semantic-pull-request.yml (+12 -1)
📝 .gitignore (+5 -0)
📝 demo/electron/package.json (+1 -1)
📝 demo/electron/pnpm-lock.yaml (+21 -15)
📝 demo/oidc-client/package.json (+1 -1)
📝 demo/oidc-client/pnpm-lock.yaml (+23 -23)
📝 docs/.remarkrc.mjs (+2 -0)
📝 docs/app/blog/[[...slug]]/page.tsx (+22 -1)
📝 docs/app/community/community-client.tsx (+2 -2)
📝 docs/app/globals.css (+9 -0)
📝 docs/components/community-adapters-grid.tsx (+11 -0)
📝 docs/components/community-plugins-table.tsx (+2 -2)
📝 docs/components/landing/hero-readme.tsx (+1 -1)

...and 46 more files

📄 Description

🛡️ Objective: Secure SSO Organization Isolation

This PR implements comprehensive security hardening for organization isolation when using the SSO plugin. It ensures that any user session authenticated via a specific SSO provider/organization is strictly restricted to that organization's data and management endpoints, preventing cross-tenant access.

🛠️ Key Changes

  • Centralized Isolation Guard: Implemented checkSSOIsolation to intercept and block any management requests (Teams, Invites, Members, etc.) that target a different organization than the one the user is SSO-scoped to.
  • SSO Plugin Hardening: Injected isolation checks into all critical SSO routes:
    • registerSSOProvider
    • getSSOProvider, updateSSOProvider, deleteSSOProvider
    • requestDomainVerification and verifyDomain
  • Smart Resource Filtering: Patched listOrganizations and listUserTeams to automatically filter results, ensuring users in SSO sessions only see their authorized organization and its resources.
  • Session Lifecycle Integration: Updated the SAML callback logic to automatically inject and persist the ssoOrganizationId in the session upon successful sign-in.

Verification Results

I have implemented and verified these changes using a new E2E test suite: packages/sso/src/sso-isolation.test.ts.

Test Results Summary:

  • Step 1: listOrganizations Filtering: Confirmed SSO sessions only see 1 authorized organization instead of all memberships.
  • Step 2: Team Management: Confirmed 403 Forbidden when attempting to update teams in another organization.
  • Step 3: SSO Provider Isolation: Confirmed 403 Forbidden when fetching providers from different organizations.
  • Step 4: Domain Verification: Confirmed isolation for domain ownership verification.

All tests passed successfully.


Summary by cubic

Adds strict SSO organization isolation with a closed-by-default guard across organization and SSO management routes to prevent cross-tenant access. SSO sessions are now scoped via new session fields, and endpoint instrumentation is clarified for cleaner traces.

  • New Features

    • Introduced checkSSOIsolation in better-auth and @better-auth/sso; enforced on org routes (roles, invites, members, org, teams) and SSO routes (provider CRUD, registration, domain verification). When a session has ssoOrganizationId, cross-org requests are denied.
    • @better-auth/sso now registers ssoProviderId and ssoOrganizationId as session fields and sets them during OIDC/SAML callbacks. Provider endpoints accept optional organizationId and enforce isolation.
  • Bug Fixes

    • Endpoint spans now always use the route template; plugin hook spans (onRequest/onResponse) no longer include the route in their span names.
    • checkPassword now returns INVALID_PASSWORD for all failures to prevent credential/account enumeration.
    • Fixed type errors in access-control routes and e2e tests.

Written for commit 55774a99cd. Summary will update on new commits.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/9037 **Author:** [@surendarcs](https://github.com/surendarcs) **Created:** 4/8/2026 **Status:** 🔄 Open **Base:** `next` ← **Head:** `fix/sso-org-isolation-9013` --- ### 📝 Commits (10+) - [`60123da`](https://github.com/better-auth/better-auth/commit/60123da09ce09996782a30995a8a13bf6ce0274f) docs: add missing secret to getSignedCookie and remove misleading comments (#9008) - [`3baf029`](https://github.com/better-auth/better-auth/commit/3baf0294c2e439714fdf926977ea2cf4aed06539) chore(docs): add `remark-frontmatter` (#9015) - [`374985e`](https://github.com/better-auth/better-auth/commit/374985e340643f230adeb30961a76b414af6cb7c) docs: update community adapters and plugins pages (#9014) - [`3b95d70`](https://github.com/better-auth/better-auth/commit/3b95d70d046164424f778b4c01ba17319c2eaae5) docs: change community plugin name (#8961) - [`47dc887`](https://github.com/better-auth/better-auth/commit/47dc887d589d75d486edbd34b5cff869cb48194e) docs: add `@delmaredigital/payload-better-auth` to community plugins (#8375) - [`f61ad1c`](https://github.com/better-auth/better-auth/commit/f61ad1cab7360e4460e6450904e97498298a79d5) fix: use `INVALID_PASSWORD` for all `checkPassword` failures (#8902) - [`465c1dc`](https://github.com/better-auth/better-auth/commit/465c1dccf669f167cd03eb748df5ee2be3d1ff98) ci: add one-click changeset commit and unify bot identity (#9019) - [`2e537df`](https://github.com/better-auth/better-auth/commit/2e537df5f7f2a4263f52cce74d7a64a0a947792b) fix: endpoint instrumentation to always use route template (#9023) - [`5e7fd61`](https://github.com/better-auth/better-auth/commit/5e7fd61da065d900afc6bc20caadd63caee0808e) docs: hide fumadocs toc scroll indicator dot (#9021) - [`a7e359d`](https://github.com/better-auth/better-auth/commit/a7e359d870890230c79c09f85e3027eb1ddeb346) docs: add expo sentinel docs (#9025) ### 📊 Changes **66 files changed** (+3037 additions, -1190 deletions) <details> <summary>View changed files</summary> ➕ `.changeset/fresh-news-doubt.md` (+5 -0) ➕ `.changeset/pr-8902.md` (+5 -0) ➕ `.changeset/sso-org-isolation.md` (+6 -0) 📝 `.github/workflows/auto-changeset.yml` (+186 -2) 📝 `.github/workflows/auto-label.yml` (+9 -0) 📝 `.github/workflows/auto-retarget.yml` (+13 -4) 📝 `.github/workflows/lock-threads.yml` (+10 -0) 📝 `.github/workflows/semantic-pull-request.yml` (+12 -1) 📝 `.gitignore` (+5 -0) 📝 `demo/electron/package.json` (+1 -1) 📝 `demo/electron/pnpm-lock.yaml` (+21 -15) 📝 `demo/oidc-client/package.json` (+1 -1) 📝 `demo/oidc-client/pnpm-lock.yaml` (+23 -23) 📝 `docs/.remarkrc.mjs` (+2 -0) 📝 `docs/app/blog/[[...slug]]/page.tsx` (+22 -1) 📝 `docs/app/community/community-client.tsx` (+2 -2) 📝 `docs/app/globals.css` (+9 -0) 📝 `docs/components/community-adapters-grid.tsx` (+11 -0) 📝 `docs/components/community-plugins-table.tsx` (+2 -2) 📝 `docs/components/landing/hero-readme.tsx` (+1 -1) _...and 46 more files_ </details> ### 📄 Description ## 🛡️ Objective: Secure SSO Organization Isolation This PR implements comprehensive security hardening for organization isolation when using the SSO plugin. It ensures that any user session authenticated via a specific SSO provider/organization is strictly restricted to that organization's data and management endpoints, preventing cross-tenant access. ### 🛠️ Key Changes - **Centralized Isolation Guard**: Implemented `checkSSOIsolation` to intercept and block any management requests (Teams, Invites, Members, etc.) that target a different organization than the one the user is SSO-scoped to. - **SSO Plugin Hardening**: Injected isolation checks into all critical SSO routes: - `registerSSOProvider` - `getSSOProvider`, `updateSSOProvider`, `deleteSSOProvider` - `requestDomainVerification` and `verifyDomain` - **Smart Resource Filtering**: Patched `listOrganizations` and `listUserTeams` to automatically filter results, ensuring users in SSO sessions only see their authorized organization and its resources. - **Session Lifecycle Integration**: Updated the SAML callback logic to automatically inject and persist the `ssoOrganizationId` in the session upon successful sign-in. ### ✅ Verification Results I have implemented and verified these changes using a new E2E test suite: `packages/sso/src/sso-isolation.test.ts`. **Test Results Summary:** - **Step 1: listOrganizations Filtering**: Confirmed SSO sessions only see 1 authorized organization instead of all memberships. - **Step 2: Team Management**: Confirmed `403 Forbidden` when attempting to update teams in another organization. - **Step 3: SSO Provider Isolation**: Confirmed `403 Forbidden` when fetching providers from different organizations. - **Step 4: Domain Verification**: Confirmed isolation for domain ownership verification. All tests passed successfully. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Adds strict SSO organization isolation with a closed-by-default guard across organization and SSO management routes to prevent cross-tenant access. SSO sessions are now scoped via new session fields, and endpoint instrumentation is clarified for cleaner traces. - New Features - Introduced `checkSSOIsolation` in `better-auth` and `@better-auth/sso`; enforced on org routes (roles, invites, members, org, teams) and SSO routes (provider CRUD, registration, domain verification). When a session has `ssoOrganizationId`, cross-org requests are denied. - `@better-auth/sso` now registers `ssoProviderId` and `ssoOrganizationId` as session fields and sets them during OIDC/SAML callbacks. Provider endpoints accept optional `organizationId` and enforce isolation. - Bug Fixes - Endpoint spans now always use the route template; plugin hook spans (`onRequest`/`onResponse`) no longer include the route in their span names. - `checkPassword` now returns `INVALID_PASSWORD` for all failures to prevent credential/account enumeration. - Fixed type errors in access-control routes and e2e tests. <sup>Written for commit 55774a99cdacfbec1f315dcd9fa261cf16e027cc. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-13 10:37:25 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#16638