[GH-ISSUE #9013] User logged in via SSO of one Org able to access other. #19887

Closed
opened 2026-04-15 19:14:57 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @opswiz on GitHub (Apr 7, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/9013

Originally assigned to: @ping-maxwell on GitHub.

Is this suited for github?

  • Yes, this is suited for github

Reproduction

  1. Create Two Orgs
  2. Configure different SSO providers in the org
  3. Login to the app using one IDP
  4. You can access both the organizations

Current vs. Expected behavior

Currently there is an issue, Lets say user belong to multiple organization. Where each organization have different SSO configured. If user logs in to the application via SSO from the IDP he gets redirected back to the app and a session is created, by default he is able to access both the organization even for the one which he did not authentication with SSO.

isn't this bad? shouldn't he be able to access only the org which he SSO'ed into? Am missing something or missing any configuration?

What version of Better Auth are you using?

1.5.6

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.4.0: Thu Mar 19 19:33:09 PDT 2026; root:xnu-12377.101.15~1/RELEASE_ARM64_T8112",
    "release": "25.4.0",
    "cpuCount": 8,
    "cpuModel": "Apple M2",
    "totalMemory": "8.00 GB",
    "freeMemory": "0.11 GB"
  },
  "node": {
    "version": "v24.14.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.9.0"
  },
  "frameworks": [
    {
      "name": "react",
      "version": "19.2.4"
    },
    {
      "name": "hono",
      "version": "^4.12.11"
    }
  ],
  "databases": [
    {
      "name": "@prisma/client",
      "version": "^7.6.0"
    }
  ],
  "betterAuth": {
    "version": "^1.5.6",
    "config": {
      "secret": "[REDACTED]",
      "baseURL": "http://localhost:5150",
      "session": {
        "expiresIn": 172800,
        "updateAge": 86400,
        "cookieCache": {
          "enabled": true,
          "maxAge": 300
        }
      },
      "advanced": {
        "trustedProxyHeaders": true,
        "defaultCookieAttributes": {
          "sameSite": "Strict"
        }
      },
      "account": {
        "accountLinking": {
          "allowDifferentEmails": false,
          "allowUnlinkingAll": true
        }
      },
      "emailVerification": {
        "autoSignInAfterVerification": true
      },
      "emailAndPassword": {
        "enabled": false
      },
      "databaseHooks": {
        "user": {
          "create": {}
        }
      },
      "plugins": [
        {
          "name": "sso",
          "config": {
            "id": "sso",
            "endpoints": {},
            "hooks": {
              "before": [
                {}
              ],
              "after": [
                {}
              ]
            },
            "schema": {
              "ssoProvider": {
                "modelName": "ssoProvider",
                "fields": {
                  "issuer": {
                    "type": "string",
                    "required": true,
                    "fieldName": "issuer"
                  },
                  "oidcConfig": {
                    "type": "string",
                    "required": false,
                    "fieldName": "oidcConfig"
                  },
                  "samlConfig": {
                    "type": "string",
                    "required": false,
                    "fieldName": "samlConfig"
                  },
                  "userId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "fieldName": "userId"
                  },
                  "providerId": {
                    "type": "string",
                    "required": true,
                    "unique": true,
                    "fieldName": "providerId"
                  },
                  "organizationId": {
                    "type": "string",
                    "required": false,
                    "fieldName": "organizationId"
                  },
                  "domain": {
                    "type": "string",
                    "required": true,
                    "fieldName": "domain"
                  },
                  "domainVerified": {
                    "type": "boolean",
                    "required": false
                  }
                }
              }
            },
            "options": {
              "domainVerification": {
                "enabled": true,
                "tokenPrefix": "ops-dv"
              },
              "organizationProvisioning": {
                "disabled": true,
                "defaultRole": "member"
              }
            }
          }
        }
      ],
      "logger": {
        "disabled": false,
        "disableColors": false,
        "level": "debug"
      }
    }
  }
}

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
  plugins: [
        sso({
          domainVerification: {
            enabled: true,
          },
          organizationProvisioning: {
            disabled: true, // disable org provisioning
            defaultRole: 'member', // Default role for new members
          },
    }),
  ]
});

Additional context

No response

Originally created by @opswiz on GitHub (Apr 7, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/9013 Originally assigned to: @ping-maxwell on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### Reproduction 1. Create Two Orgs 2. Configure different SSO providers in the org 3. Login to the app using one IDP 4. You can access both the organizations ### Current vs. Expected behavior Currently there is an issue, Lets say user belong to multiple organization. Where each organization have different SSO configured. If user logs in to the application via SSO from the IDP he gets redirected back to the app and a session is created, by default he is able to access both the organization even for the one which he did not authentication with SSO. isn't this bad? shouldn't he be able to access only the org which he SSO'ed into? Am missing something or missing any configuration? ### What version of Better Auth are you using? 1.5.6 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.4.0: Thu Mar 19 19:33:09 PDT 2026; root:xnu-12377.101.15~1/RELEASE_ARM64_T8112", "release": "25.4.0", "cpuCount": 8, "cpuModel": "Apple M2", "totalMemory": "8.00 GB", "freeMemory": "0.11 GB" }, "node": { "version": "v24.14.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.9.0" }, "frameworks": [ { "name": "react", "version": "19.2.4" }, { "name": "hono", "version": "^4.12.11" } ], "databases": [ { "name": "@prisma/client", "version": "^7.6.0" } ], "betterAuth": { "version": "^1.5.6", "config": { "secret": "[REDACTED]", "baseURL": "http://localhost:5150", "session": { "expiresIn": 172800, "updateAge": 86400, "cookieCache": { "enabled": true, "maxAge": 300 } }, "advanced": { "trustedProxyHeaders": true, "defaultCookieAttributes": { "sameSite": "Strict" } }, "account": { "accountLinking": { "allowDifferentEmails": false, "allowUnlinkingAll": true } }, "emailVerification": { "autoSignInAfterVerification": true }, "emailAndPassword": { "enabled": false }, "databaseHooks": { "user": { "create": {} } }, "plugins": [ { "name": "sso", "config": { "id": "sso", "endpoints": {}, "hooks": { "before": [ {} ], "after": [ {} ] }, "schema": { "ssoProvider": { "modelName": "ssoProvider", "fields": { "issuer": { "type": "string", "required": true, "fieldName": "issuer" }, "oidcConfig": { "type": "string", "required": false, "fieldName": "oidcConfig" }, "samlConfig": { "type": "string", "required": false, "fieldName": "samlConfig" }, "userId": { "type": "string", "references": { "model": "user", "field": "id" }, "fieldName": "userId" }, "providerId": { "type": "string", "required": true, "unique": true, "fieldName": "providerId" }, "organizationId": { "type": "string", "required": false, "fieldName": "organizationId" }, "domain": { "type": "string", "required": true, "fieldName": "domain" }, "domainVerified": { "type": "boolean", "required": false } } } }, "options": { "domainVerification": { "enabled": true, "tokenPrefix": "ops-dv" }, "organizationProvisioning": { "disabled": true, "defaultRole": "member" } } } } ], "logger": { "disabled": false, "disableColors": false, "level": "debug" } } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, plugins: [ sso({ domainVerification: { enabled: true, }, organizationProvisioning: { disabled: true, // disable org provisioning defaultRole: 'member', // Default role for new members }, }), ] }); ``` ### Additional context _No response_
GiteaMirror added the enterprisesecurity labels 2026-04-15 19:14:57 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Apr 7, 2026):

You're correct that this is the current behavior — Better Auth sessions are not scoped to a specific organization based on which SSO provider was used for authentication. When a user logs in via SSO, the session grants access to all organizations the user is a member of. Organization access is controlled through membership validation at the endpoint level, not through session-level restrictions [1] [2].

The SSO login flow works like this: after authentication, the assignOrganizationFromProvider() function adds the user to the organization linked to that SSO provider [3], but the session metadata itself only stores OAuth tokens, provider ID, account ID, and scopes — no organization context [4]. The organization plugin does add an activeOrganizationId field to sessions [5], but this only tracks the user's current working context and doesn't restrict access to other organizations.

So you're not missing a configuration — there's no built-in option to restrict organization access based on which SSO provider was used. To implement this, you'd need a custom approach, such as:

  1. Using hooks/middleware to check which SSO provider authenticated the session and enforce that the user can only set their active organization to the one associated with that provider
  2. Requiring re-authentication when a user tries to switch to an organization with a different SSO provider — you'd need to implement this in your application logic by mapping organizations to their SSO providers and prompting re-auth on switch

This does seem like a legitimate gap for multi-org SSO deployments where each org has its own IDP and you want to enforce that authentication is org-scoped. You may want to consider this a feature request for built-in support for organization-scoped SSO sessions.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:4199422313 --> @dosubot[bot] commented on GitHub (Apr 7, 2026): <!-- Answer --> You're correct that this is the current behavior — **Better Auth sessions are not scoped to a specific organization based on which SSO provider was used for authentication**. When a user logs in via SSO, the session grants access to all organizations the user is a member of. Organization access is controlled through membership validation at the endpoint level, not through session-level restrictions [[1]](https://github.com/better-auth/better-auth/blob/0e3f188020fa785d44d0019a3a3c8407a4cc8d5f/packages/better-auth/src/plugins/organization/adapter.ts) [[2]](https://github.com/better-auth/better-auth/blob/0e3f188020fa785d44d0019a3a3c8407a4cc8d5f/packages/sso/src/routes/sso.ts). The SSO login flow works like this: after authentication, the `assignOrganizationFromProvider()` function adds the user to the organization linked to that SSO provider [[3]](https://github.com/better-auth/better-auth/blob/0e3f188020fa785d44d0019a3a3c8407a4cc8d5f/packages/sso/src/routes/sso.ts#L1777-L1790), but the session metadata itself only stores OAuth tokens, provider ID, account ID, and scopes — **no organization context** [[4]](https://github.com/better-auth/better-auth/blob/0e3f188020fa785d44d0019a3a3c8407a4cc8d5f/packages/sso/src/routes/sso.ts#L1735-L1754). The organization plugin does add an `activeOrganizationId` field to sessions [[5]](https://github.com/better-auth/better-auth/blob/0e3f188020fa785d44d0019a3a3c8407a4cc8d5f/packages/better-auth/src/plugins/organization/schema.ts#L198-L203), but this only tracks the user's current working context and doesn't restrict access to other organizations. So you're not missing a configuration — there's no built-in option to restrict organization access based on which SSO provider was used. To implement this, you'd need a custom approach, such as: 1. **Using hooks/middleware** to check which SSO provider authenticated the session and enforce that the user can only set their active organization to the one associated with that provider 2. **Requiring re-authentication** when a user tries to switch to an organization with a different SSO provider — you'd need to implement this in your application logic by mapping organizations to their SSO providers and prompting re-auth on switch This does seem like a legitimate gap for multi-org SSO deployments where each org has its own IDP and you want to enforce that authentication is org-scoped. You may want to consider this a feature request for built-in support for organization-scoped SSO sessions. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=brand-link).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-great_response#message_id=6728e104-015e-4c5e-8ed6-e3df76f500b4&feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-irrelevant_answer#message_id=6728e104-015e-4c5e-8ed6-e3df76f500b4&feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-incorrect_sources#message_id=6728e104-015e-4c5e-8ed6-e3df76f500b4&feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-too_verbose#message_id=6728e104-015e-4c5e-8ed6-e3df76f500b4&feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-hallucination#message_id=6728e104-015e-4c5e-8ed6-e3df76f500b4&feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-bug_report#message_id=6728e104-015e-4c5e-8ed6-e3df76f500b4&feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-other#message_id=6728e104-015e-4c5e-8ed6-e3df76f500b4&feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=chat-badge)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=You%27re%20correct%20that%20this%20is%20the%20current%20behavior%20%E2%80%94%20%2A%2ABetter%20Auth%20sessions%20are%20not%20scoped%20to%20a%20specific%20organization%20based%20on%20which%20SSO%20provider%20was%20used%20for%20authentication%2A%2A.%20When%20a%20user%20logs%20in%20via%20SSO%2C%20the%20session%20grants%20access%20to%20all%20organizations%20the%20user%20is%20a%20member%20of.%20Organization%20access%20is%20controlled%20through%20membership%20validation%20at%20the%20endpoint%20level%2C%20not%20through%20session-level%20restrictions%20%5B%5B1%5D%5D%28https%3A//github.com/better-auth/better-auth/blob/0e3f188020fa785d44d0019a3a3c8407a4cc8d5f/packages/better-auth/src/plugins/organization/adapter.ts%29%20%5B%5B2%5D%5D%28https%3A//github.com/better-auth/better-auth/blob/0e3f188020fa785d44d0019a3a3c8407a4cc8d5f/packages/sso/src/routes/sso.ts%29.%0A%0AThe%20SSO%20login%20flow%20works%20like%20this%3A%20after%20authentication%2C%20the%20%60assignOrganizationFromProvider%28%29%60%20function%20adds%20the%20user%20to%20the%20organization%20linked%20to%20that%20SSO%20provider%20%5B%5B3%5D%5D%28https%3A//github.com/better-auth/better-auth/blob/0e3f188020fa785d44d0019a3a3c8407a4cc8d5f/packages/sso/src/routes/sso.ts%23L1777-L1790%29%2C%20but%20the%20session%20metadata%20itself%20only%20stores%20OAuth%20tokens%2C%20provider%20ID%2C%20account%20ID%2C%20and%20scopes%20%E2%80%94%20%2A%2Ano%20organization%20context%2A%2A%20%5B%5B4%5D%5D%28https%3A//github.com/better-auth/better-auth/blob/0e3f188020fa785d44d0019a3a3c8407a4cc8d5f/packages/sso/src/routes/sso.ts%23L1735-L1754%29.%20The%20organization%20plugin%20does%20add%20an%20%60activeOrganizationId%60%20field%20to%20sessions%20%5B%5B5%5D%5D%28https%3A//github.com/better-auth/better-auth/blob/0e3f188020fa785d44d0019a3a3c8407a4cc8d5f/packages/better-auth/src/plugins/organization/schema.ts%23L198-L203%29%2C%20but%20this%20only%20tracks%20the%20user%27s%20current%20working%20context%20and%20doesn%27t%20restrict%20access%20to%20other%20organizations.%0A%0ASo%20you%27re%20not%20missing%20a%20configuration%20%E2%80%94%20there%27s%20no%20built-in%20option%20to%20restrict%20organization%20access%20based%20on%20which%20SSO%20provider%20was%20used.%20To%20implement%20this%2C%20you%27d%20need%20a%20custom%20approach%2C%20such%20as%3A%0A%0A1.%20%2A%2AUsing%20hooks/middleware%2A%2A%20to%20check%20which%20SSO%20provider%20authenticated%20the%20session%20and%20enforce%20that%20the%20user%20can%20only%20set%20their%20active%20organization%20to%20the%20one%20associated%20with%20that%20provider%0A2.%20%2A%2ARequiring%20re-authentication%2A%2A%20when%20a%20user%20tries%20to%20switch%20to%20an%20organization%20with%20a%20different%20SSO%20provider%20%E2%80%94%20you%27d%20need%20to%20implement%20this%20in%20your%20application%20logic%20by%20mapping%20organizations%20to%20their%20SSO%20providers%20and%20prompting%20re-auth%20on%20switch%0A%0AThis%20does%20seem%20like%20a%20legitimate%20gap%20for%20multi-org%20SSO%20deployments%20where%20each%20org%20has%20its%20own%20IDP%20and%20you%20want%20to%20enforce%20that%20authentication%20is%20org-scoped.%20You%20may%20want%20to%20consider%20this%20a%20feature%20request%20for%20built-in%20support%20for%20organization-scoped%20SSO%20sessions.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=join-discord)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/9013)
Author
Owner

@ping-maxwell commented on GitHub (Apr 7, 2026):

Thanks for reporting this. After discussing internally, we've decided not to address this at the library level for now. While we understand the concern, having a single user in multiple organizations with separate SSO providers is a rare configuration, and in those cases we think the enforcement is best handled at the application level (for example, by checking which SSO provider the user authenticated through before granting access to a specific org's resources).
That said, the SSO plugin does already set activeOrganizationId after login, so the session is scoped to the authenticated org by default. If this becomes a more common request, we're open to revisiting it in a future release.

<!-- gh-comment-id:4202584698 --> @ping-maxwell commented on GitHub (Apr 7, 2026): Thanks for reporting this. After discussing internally, we've decided not to address this at the library level for now. While we understand the concern, having a single user in multiple organizations with separate SSO providers is a rare configuration, and in those cases we think the enforcement is best handled at the application level (for example, by checking which SSO provider the user authenticated through before granting access to a specific org's resources). That said, the SSO plugin does already set `activeOrganizationId` after login, so the session is scoped to the authenticated org by default. If this becomes a more common request, we're open to revisiting it in a future release.
Author
Owner

@opswiz commented on GitHub (Apr 8, 2026):

Thanks for the reply. @ping-maxwell

That said, the SSO plugin does already set activeOrganizationId after login, so the session is scoped to the authenticated org by default. If this becomes a more common request, we're open to revisiting it in a future release.

Can you confirm this? I don't seem to find it not it works for me, nor I can find any code for the same. I see this as part of the PR you created, but got closed as well. Currently we have to take workarounds in our application to implement this. Can we get this fixed at-least?

And there is also this https://github.com/better-auth/better-auth/issues/9031

, having a single user in multiple organizations with separate SSO providers is a rare configuration,

I agree this is a rare configuration, may not happen with regular users, but is not rare when it comes to enterprises where multiple organizations and shared users are common, consider different Business Units. Also Its not about different SSO provider, its about different SSO configuration. One provider Okta, App1(bu1) -> Org1, App2(bu2) -> Org2. I believe, github also works the same way.

Also, Its not about how much users will do it. Its more about the correctness of the implementation and leaving it to fail when the rare case happens. And someone will find out this hard way compromising their security. IMO, I don't think its okay let gaps and wait for someone exploit that or face some consequences to fix it.

in those cases we think the enforcement is best handled at the application level (for example, by checking which SSO provider the user authenticated through before granting access to a specific org's resources)

Yes, we have already started to do that. but I would urge to re-consider the decision, if authentication and access control is the libraries goal and this should be covered correctly as well. People already have better-auth in their production and find at the hard way is going to be a blow back.

Kindly re-consider the enterprise scenarios too not just regular users point of view.

<!-- gh-comment-id:4204154534 --> @opswiz commented on GitHub (Apr 8, 2026): Thanks for the reply. @ping-maxwell > That said, the SSO plugin does already set activeOrganizationId after login, so the session is scoped to the authenticated org by default. If this becomes a more common request, we're open to revisiting it in a future release. Can you confirm this? I don't seem to find it not it works for me, nor I can find any code for the same. I see this as part of the PR you created, but got closed as well. Currently we have to take workarounds in our application to implement this. Can we get this fixed at-least? And there is also this https://github.com/better-auth/better-auth/issues/9031 > , having a single user in multiple organizations with separate SSO providers is a rare configuration, I agree this is a rare configuration, may not happen with regular users, but is not rare when it comes to enterprises where multiple organizations and shared users are common, consider different Business Units. Also Its not about different SSO provider, its about different SSO configuration. One provider Okta, App1(bu1) -> Org1, App2(bu2) -> Org2. I believe, github also works the same way. Also, Its not about how much users will do it. Its more about the correctness of the implementation and leaving it to fail when the rare case happens. And someone will find out this hard way compromising their security. IMO, I don't think its okay let gaps and wait for someone exploit that or face some consequences to fix it. > in those cases we think the enforcement is best handled at the application level (for example, by checking which SSO provider the user authenticated through before granting access to a specific org's resources) Yes, we have already started to do that. but I would urge to re-consider the decision, if authentication and access control is the libraries goal and this should be covered correctly as well. People already have better-auth in their production and find at the hard way is going to be a blow back. Kindly re-consider the enterprise scenarios too not just regular users point of view.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#19887