disableImplicitSignUp not used for for SAML-based SSO providers #2300

Closed
opened 2026-03-13 09:42:27 -05:00 by GiteaMirror · 8 comments
Owner

Originally created by @kanarian on GitHub (Nov 13, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Install the @better-auth/sso plugin.
  2. In your auth configurations, add a SAML-based defaultSSO provider such as DummyIDP.
  3. Inside of your auth configurations, set disableImplictSignUp to true.
  4. Use the Identity Provider to login to your application using an email-address that does not exist in your database.

Current vs. Expected behavior

Following the previous steps, I expected that, since the email-address did not exist and disableImplicitSignUp is true, that the user would not be allowed to login, because they do not have an account and implicit sign up is disabled.

However, to my surprise, an Account entry and a User entry do get created for this email-address.

It seems like disableImplicitSignUp is ignored.

I found that the callbackSSOSAML function does not check for disableImplicitSignUp when creating a User or Account if one does not exist (e.g. here in the source code).

I noticed that the callbackSSO function does not directly create a User or Account, but uses the handleOAuthUserInfo function. handleOAuthUserInfo then uses implicitDisableSignUp to ensure that neither an Account nor a User is created.

Is there any reason as to why the callbackSSOSAML does not use the handleOAuthUserInfo function? Could be a way to fix the bug I reckon. Thanks!

What version of Better Auth are you using?

1.3.34

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 23.6.0: Mon Jul 29 21:14:21 PDT 2024; root:xnu-10063.141.2~1/RELEASE_ARM64_T8103",
    "release": "23.6.0",
    "cpuCount": 8,
    "cpuModel": "Apple M1",
    "totalMemory": "16.00 GB",
    "freeMemory": "0.12 GB"
  },
  "node": {
    "version": "v22.15.1",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.6.2"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "^15.5.5"
    },
    {
      "name": "react",
      "version": "18.2.0"
    }
  ],
  "databases": [
    {
      "name": "@prisma/client",
      "version": "^6.18.0"
    }
  ],
  "betterAuth": {
    "version": "^1.3.34",
    "config": {
      "logger": {
        "level": "debug"
      },
      "trustedOrigins": [LIST_OF_TRUSTED_ORIGINS],
      "emailAndPassword": {
        "enabled": true,
        "requireEmailVerification": true,
        "minPasswordLength": 12,
        "autoSignIn": true,
        "password": {}
      },
      "emailVerification": {
        "enabled": true,
        "sendOnSignUp": true,
        "autoSignInAfterVerification": false,
        "expiresIn": 3600
      },
      "hooks": {},
      "account": {
        "modelName": "Account",
        "accountLinking": {
          "enabled": true,
          "trustedProviders": [
            "sso"
          ]
        }
      },
      "session": {
        "modelName": "Session",
        "cookieCache": {
          "enabled": false
        }
      },
      "user": {
        "modelName": "User",
        "fields": {
          "emailVerified": "emailVerifiedBoolean"
        },
        "additionalFields": {
          "language": {
            "type": "string",
            "required": true,
            "default": "NL",
            "enum": [
              "NL",
              "EN"
            ]
          },
          "firstName": {
            "type": "string",
            "required": true
          },
          "lastName": {
            "type": "string",
            "required": true
          },
          "codeUsedToRegister": {
            "type": "string",
            "required": false
          }
        }
      },
      "verification": {
        "modelName": "Verification"
      },
      "plugins": [
        {
          "name": "sso",
          "config": {
            "id": "sso",
            "endpoints": {},
            "schema": {
              "ssoProvider": {
                "fields": {
                  "issuer": {
                    "type": "string",
                    "required": true
                  },
                  "oidcConfig": {
                    "type": "string",
                    "required": false
                  },
                  "samlConfig": {
                    "type": "string",
                    "required": false
                  },
                  "userId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id"
                    }
                  },
                  "providerId": {
                    "type": "string",
                    "required": true,
                    "unique": true
                  },
                  "organizationId": {
                    "type": "string",
                    "required": false
                  },
                  "domain": {
                    "type": "string",
                    "required": true
                  }
                }
              }
            }
          }
        },
        {
          "name": "custom-session",
          "config": {
            "id": "custom-session",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {}
          }
        },
        {
          "name": "email-otp",
          "config": {
            "id": "email-otp",
            "endpoints": {},
            "hooks": {
              "after": [
                {}
              ]
            },
            "$ERROR_CODES": {
              "OTP_EXPIRED": "otp expired",
              "INVALID_OTP": "Invalid OTP",
              "INVALID_EMAIL": "Invalid email",
              "USER_NOT_FOUND": "User not found",
              "TOO_MANY_ATTEMPTS": "Too many attempts"
            },
            "rateLimit": [
              {
                "window": 60,
                "max": 3
              },
              {
                "window": 60,
                "max": 3
              },
              {
                "window": 60,
                "max": 3
              },
              {
                "window": 60,
                "max": 3
              }
            ]
          }
        },
        {
          "name": "admin",
          "config": {
            "id": "admin",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "$ERROR_CODES": {
              "FAILED_TO_CREATE_USER": "Failed to create user",
              "USER_ALREADY_EXISTS": "User already exists.",
              "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL": "User already exists. Use another email.",
              "YOU_CANNOT_BAN_YOURSELF": "You cannot ban yourself",
              "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role",
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": "You are not allowed to create users",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": "You are not allowed to list users",
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": "You are not allowed to list users sessions",
              "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": "You are not allowed to ban users",
              "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": "You are not allowed to impersonate users",
              "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": "You are not allowed to revoke users sessions",
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": "You are not allowed to delete users",
              "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": "[REDACTED]",
              "BANNED_USER": "You have been banned from this application",
              "YOU_ARE_NOT_ALLOWED_TO_GET_USER": "You are not allowed to get user",
              "NO_DATA_TO_UPDATE": "No data to update",
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": "You are not allowed to update users",
              "YOU_CANNOT_REMOVE_YOURSELF": "You cannot remove yourself",
              "YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE": "You are not allowed to set a non-existent role value"
            },
            "schema": {
              "user": {
                "fields": {
                  "role": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banned": {
                    "type": "boolean",
                    "defaultValue": false,
                    "required": false,
                    "input": false
                  },
                  "banReason": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banExpires": {
                    "type": "date",
                    "required": false,
                    "input": false
                  }
                }
              },
              "session": {
                "fields": {
                  "impersonatedBy": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            },
            "options": {
              "adminRoles": [
                "ADMIN"
              ],
              "defaultRole": "USER",
              "roles": {
                "COACH": {
                  "statements": {
                    "user": [
                      "create"
                    ]
                  }
                }
              }
            }
          }
        },
        {
          "name": "next-cookies",
          "config": {
            "id": "next-cookies",
            "hooks": {
              "after": [
                {}
              ]
            }
          }
        }
      ]
    }
  }
}

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

Backend

Auth config (if applicable)


Additional context

No response

Originally created by @kanarian on GitHub (Nov 13, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Install the `@better-auth/sso` plugin. 2. In your auth configurations, add a **SAML-based** `defaultSSO` provider such as [DummyIDP](https://dummyidp.com/). 3. Inside of your auth configurations, set `disableImplictSignUp` to `true`. 4. Use the Identity Provider to login to your application using an email-address that does not exist in your database. ### Current vs. Expected behavior Following the previous steps, I expected that, since the email-address did not exist and `disableImplicitSignUp` is true, that the user would not be allowed to login, because they do not have an account and implicit sign up is disabled. However, to my surprise, an Account entry and a User entry do get created for this email-address. It seems like `disableImplicitSignUp` is ignored. I found that the [`callbackSSOSAML` function](https://github.com/better-auth/better-auth/blob/ea1cf04d76cd45105a99363b62069f33dbbf2a42/packages/sso/src/routes/sso.ts#L1280) does not check for `disableImplicitSignUp` when creating a User or Account if one does not exist (e.g. [here in the source code](https://github.com/better-auth/better-auth/blob/ea1cf04d76cd45105a99363b62069f33dbbf2a42/packages/sso/src/routes/sso.ts#L1587)). I noticed that the `callbackSSO` function does not directly create a User or Account, but uses the `handleOAuthUserInfo` function. `handleOAuthUserInfo` then uses `implicitDisableSignUp` to ensure that neither an Account nor a User is created. Is there any reason as to why the `callbackSSOSAML` does not use the `handleOAuthUserInfo` function? Could be a way to fix the bug I reckon. Thanks! ### What version of Better Auth are you using? 1.3.34 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 23.6.0: Mon Jul 29 21:14:21 PDT 2024; root:xnu-10063.141.2~1/RELEASE_ARM64_T8103", "release": "23.6.0", "cpuCount": 8, "cpuModel": "Apple M1", "totalMemory": "16.00 GB", "freeMemory": "0.12 GB" }, "node": { "version": "v22.15.1", "env": "development" }, "packageManager": { "name": "npm", "version": "11.6.2" }, "frameworks": [ { "name": "next", "version": "^15.5.5" }, { "name": "react", "version": "18.2.0" } ], "databases": [ { "name": "@prisma/client", "version": "^6.18.0" } ], "betterAuth": { "version": "^1.3.34", "config": { "logger": { "level": "debug" }, "trustedOrigins": [LIST_OF_TRUSTED_ORIGINS], "emailAndPassword": { "enabled": true, "requireEmailVerification": true, "minPasswordLength": 12, "autoSignIn": true, "password": {} }, "emailVerification": { "enabled": true, "sendOnSignUp": true, "autoSignInAfterVerification": false, "expiresIn": 3600 }, "hooks": {}, "account": { "modelName": "Account", "accountLinking": { "enabled": true, "trustedProviders": [ "sso" ] } }, "session": { "modelName": "Session", "cookieCache": { "enabled": false } }, "user": { "modelName": "User", "fields": { "emailVerified": "emailVerifiedBoolean" }, "additionalFields": { "language": { "type": "string", "required": true, "default": "NL", "enum": [ "NL", "EN" ] }, "firstName": { "type": "string", "required": true }, "lastName": { "type": "string", "required": true }, "codeUsedToRegister": { "type": "string", "required": false } } }, "verification": { "modelName": "Verification" }, "plugins": [ { "name": "sso", "config": { "id": "sso", "endpoints": {}, "schema": { "ssoProvider": { "fields": { "issuer": { "type": "string", "required": true }, "oidcConfig": { "type": "string", "required": false }, "samlConfig": { "type": "string", "required": false }, "userId": { "type": "string", "references": { "model": "user", "field": "id" } }, "providerId": { "type": "string", "required": true, "unique": true }, "organizationId": { "type": "string", "required": false }, "domain": { "type": "string", "required": true } } } } } }, { "name": "custom-session", "config": { "id": "custom-session", "hooks": { "after": [ {} ] }, "endpoints": {} } }, { "name": "email-otp", "config": { "id": "email-otp", "endpoints": {}, "hooks": { "after": [ {} ] }, "$ERROR_CODES": { "OTP_EXPIRED": "otp expired", "INVALID_OTP": "Invalid OTP", "INVALID_EMAIL": "Invalid email", "USER_NOT_FOUND": "User not found", "TOO_MANY_ATTEMPTS": "Too many attempts" }, "rateLimit": [ { "window": 60, "max": 3 }, { "window": 60, "max": 3 }, { "window": 60, "max": 3 }, { "window": 60, "max": 3 } ] } }, { "name": "admin", "config": { "id": "admin", "hooks": { "after": [ {} ] }, "endpoints": {}, "$ERROR_CODES": { "FAILED_TO_CREATE_USER": "Failed to create user", "USER_ALREADY_EXISTS": "User already exists.", "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL": "User already exists. Use another email.", "YOU_CANNOT_BAN_YOURSELF": "You cannot ban yourself", "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": "You are not allowed to change users role", "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": "You are not allowed to create users", "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": "You are not allowed to list users", "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": "You are not allowed to list users sessions", "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": "You are not allowed to ban users", "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": "You are not allowed to impersonate users", "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": "You are not allowed to revoke users sessions", "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": "You are not allowed to delete users", "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": "[REDACTED]", "BANNED_USER": "You have been banned from this application", "YOU_ARE_NOT_ALLOWED_TO_GET_USER": "You are not allowed to get user", "NO_DATA_TO_UPDATE": "No data to update", "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": "You are not allowed to update users", "YOU_CANNOT_REMOVE_YOURSELF": "You cannot remove yourself", "YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE": "You are not allowed to set a non-existent role value" }, "schema": { "user": { "fields": { "role": { "type": "string", "required": false, "input": false }, "banned": { "type": "boolean", "defaultValue": false, "required": false, "input": false }, "banReason": { "type": "string", "required": false, "input": false }, "banExpires": { "type": "date", "required": false, "input": false } } }, "session": { "fields": { "impersonatedBy": { "type": "string", "required": false } } } }, "options": { "adminRoles": [ "ADMIN" ], "defaultRole": "USER", "roles": { "COACH": { "statements": { "user": [ "create" ] } } } } } }, { "name": "next-cookies", "config": { "id": "next-cookies", "hooks": { "after": [ {} ] } } } ] } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript ``` ### Additional context _No response_
GiteaMirror added the bug label 2026-03-13 09:42:27 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 13, 2025):

(tag @better-auth-agent if you would like an answer from the Agent)

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Nov 13, 2025): (tag @better-auth-agent if you would like an answer from the Agent) <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@kanarian commented on GitHub (Nov 13, 2025):

@better-auth-agent

@kanarian commented on GitHub (Nov 13, 2025): @better-auth-agent
Author
Owner

@better-auth-agent[bot] commented on GitHub (Nov 13, 2025):

Thanks for reporting this! It appears that when using a SAML-based SSO provider with the SSO plugin, the callback route for SAML is bypassing the usual user creation flow that respects the disableImplicitSignUp flag. Instead of calling the shared handleOAuthUserInfo function—which properly checks for disableImplicitSignUp—the callbackSSOSAML function directly creates a user and account, leading to implicit sign up even when it should be disabled [1][2].

As a next step, we’re looking into aligning the SAML callback with the expected behavior of handleOAuthUserInfo. In the meantime, you might consider intercepting the callback or adding additional middleware to verify the flag before completing the sign-in process as a temporary workaround.

Tag @better-auth-agent if you would like an answer from the Agent. Is there any additional context or a specific scenario you’re encountering that might help us further troubleshoot this issue?

DiagramDiscordGitHub

Diagram Join Star

@better-auth-agent[bot] commented on GitHub (Nov 13, 2025): Thanks for reporting this! It appears that when using a SAML-based SSO provider with the SSO plugin, the callback route for SAML is bypassing the usual user creation flow that respects the disableImplicitSignUp flag. Instead of calling the shared handleOAuthUserInfo function—which properly checks for disableImplicitSignUp—the callbackSSOSAML function directly creates a user and account, leading to implicit sign up even when it should be disabled [[1]](https://github.com/better-auth/better-auth/issues/4125)[[2]](https://github.com/better-auth/better-auth/issues/1852). As a next step, we’re looking into aligning the SAML callback with the expected behavior of handleOAuthUserInfo. In the meantime, you might consider intercepting the callback or adding additional middleware to verify the flag before completing the sign-in process as a temporary workaround. Tag @better-auth-agent if you would like an answer from the Agent. Is there any additional context or a specific scenario you’re encountering that might help us further troubleshoot this issue? <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) • [Discord](https://discord.gg/better-auth) • [GitHub](https://github.com/better-auth/better-auth) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/better-auth_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/better-auth) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/better-auth/better-auth)
Author
Owner

@rovertrack commented on GitHub (Nov 13, 2025):

while doing this did you have something like

await client.signIn.social({
		
			requestSignUp: true, // if true user gets created 
		
		});

@rovertrack commented on GitHub (Nov 13, 2025): while doing this did you have something like ``` await client.signIn.social({ requestSignUp: true, // if true user gets created }); ```
Author
Owner

@kanarian commented on GitHub (Nov 13, 2025):

while doing this did you have something like

await client.signIn.social({
		
			requestSignUp: true, // if true user gets created 
		
		});

I did not, since I'm testing IdP-initiated SAML flows

@kanarian commented on GitHub (Nov 13, 2025): > while doing this did you have something like > > ``` > await client.signIn.social({ > > requestSignUp: true, // if true user gets created > > }); > ``` I did not, since I'm testing IdP-initiated SAML flows
Author
Owner

@rovertrack commented on GitHub (Nov 13, 2025):

try once with requestsignup false

@rovertrack commented on GitHub (Nov 13, 2025): try once with requestsignup false
Author
Owner

@kanarian commented on GitHub (Nov 13, 2025):

try once with requestsignup false

I set up a SP-initiated SAML flow with the following action:

const result = await authClient.signIn.sso({
          providerId: "sso",
          callbackURL: "/dashboard",
          requestSignUp: false,
});

but unfortunately, this still causes implicit sign-ups.

@kanarian commented on GitHub (Nov 13, 2025): > try once with requestsignup false I set up a SP-initiated SAML flow with the following action: ```ts const result = await authClient.signIn.sso({ providerId: "sso", callbackURL: "/dashboard", requestSignUp: false, }); ``` but unfortunately, this still causes implicit sign-ups.
Author
Owner

@kanarian commented on GitHub (Nov 14, 2025):

Closing this issue since my PR with a fix got merged

https://github.com/better-auth/better-auth/pull/5966

@kanarian commented on GitHub (Nov 14, 2025): Closing this issue since my PR with a fix got merged https://github.com/better-auth/better-auth/pull/5966
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#2300