[GH-ISSUE #1023] feat: Add separate social (OAuth) sign-up and sign-in flows #25877

Closed
opened 2026-04-17 16:11:01 -05:00 by GiteaMirror · 12 comments
Owner

Originally created by @aryanprince on GitHub (Dec 26, 2024).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/1023

Is this suited for github?

  • Yes, this is suited for github

Currently, there's no way to distinguish between OAuth sign-up vs sign-in flows. Better Auth's client API only provides authClient.signIn.social() which handles both new and existing users. This makes it impossible to:

  1. Tell new users they need to create an account first
  2. Have separate UI/UX flows for new vs returning users
  3. Handle sign-up and sign-in errors differently

Describe the solution you'd like

Add a new authClient.signUp.social() method (on the client API) specifically for new user registration via OAuth. This would:

  1. Only work for new users (error if account exists)
  2. Match how it's already used for email/password auth where authClient.signUp.email() and authClient.signIn.email() are separate functions

Example usage:

// For new users
await authClient.signUp.social()

// For existing users
await authClient.signIn.social()

Describe alternatives you've considered

I couldn't do this successfully on my own, but maybe if I manually queried the DB (using Drizzle ORM for example) to check if the returned OAuth token exists to know if it's an existing user may have worked. But again, I believe this should be part of an API from Better Auth itself.

Additional context

Having separate OAuth sign ins and sign ups would improve the user experience a lot, by making it clear whether a user is creating a new account or accessing an existing one.

But I much prefer letting the user know that they have an account or not, instead of them having to "accidentally" creating new accounts when trying to sign in with different OAuth accounts.

Backwards Compatibility Note

I've noticed that a lot of websites DO allow new users to "sign in" to create a new account when using OAuth. So maybe the API can still allow sign ups and sign ins using signIn.social() by default, and we could have an extra option like { preventSignUps: true } on the signIn.social() function so new users can't register with the "sign in" button.

Originally created by @aryanprince on GitHub (Dec 26, 2024). Original GitHub issue: https://github.com/better-auth/better-auth/issues/1023 ### Is this suited for github? - [X] Yes, this is suited for github ### Is your feature request related to a problem? Please describe. Currently, there's no way to distinguish between OAuth sign-up vs sign-in flows. Better Auth's client API only provides `authClient.signIn.social()` which handles both new and existing users. This makes it impossible to: 1. Tell new users they need to create an account first 2. Have separate UI/UX flows for new vs returning users 3. Handle sign-up and sign-in errors differently <div align="center"> <img src=https://github.com/user-attachments/assets/fc1bd1ea-ed5d-4369-aae1-fa24c6bcc653 width=400/> </div> ### Describe the solution you'd like Add a new `authClient.signUp.social()` method (on the client API) specifically for new user registration via OAuth. This would: 1. Only work for new users (error if account exists) 2. Match how it's already used for email/password auth where `authClient.signUp.email()` and `authClient.signIn.email()` are separate functions Example usage: ```ts // For new users await authClient.signUp.social() // For existing users await authClient.signIn.social() ``` ### Describe alternatives you've considered I couldn't do this successfully on my own, but maybe if I manually queried the DB (using Drizzle ORM for example) to check if the returned OAuth token exists to know if it's an existing user may have worked. But again, I believe this should be part of an API from Better Auth itself. ### Additional context Having separate OAuth sign ins and sign ups would improve the user experience a lot, by making it clear whether a user is creating a new account or accessing an existing one. But I much prefer letting the user know that they have an account or not, instead of them having to "accidentally" creating new accounts when trying to sign in with different OAuth accounts. ### Backwards Compatibility Note I've noticed that a lot of websites DO allow new users to "sign in" to create a new account when using OAuth. So maybe the API can still allow sign ups and sign ins using `signIn.social()` by default, and we could have an extra option like `{ preventSignUps: true }` on the `signIn.social()` function so new users can't register with the "sign in" button.
GiteaMirror added the locked label 2026-04-17 16:11:01 -05:00
Author
Owner

@daveycodez commented on GitHub (Dec 26, 2024):

This is interesting. On one hand, there is something nice about keeping it simple and handling both sign ups and sign ins, but I do understand the point about accidentally creating a new account, although this is a user error. I think the solution in this scenario would be for the user to log out, log in as their original account, and then be able to Link that social account even though it already belongs to another "user" object. I don't know if this works, but I think it should. So I would say the best of both worlds is

  1. Option to preventSignUps on signIn.social
  2. Allow Re-Linking of OAuth accounts that are already linked to other users
<!-- gh-comment-id:2563054445 --> @daveycodez commented on GitHub (Dec 26, 2024): This is interesting. On one hand, there is something nice about keeping it simple and handling both sign ups and sign ins, but I do understand the point about accidentally creating a new account, although this is a user error. I think the solution in this scenario would be for the user to log out, log in as their original account, and then be able to Link that social account even though it already belongs to another "user" object. I don't know if this works, but I think it should. So I would say the best of both worlds is 1. Option to preventSignUps on signIn.social 2. Allow Re-Linking of OAuth accounts that are already linked to other users
Author
Owner

@aryanprince commented on GitHub (Dec 28, 2024):

Why I want this feature

Imagine this scenario with an imaginary user Bob and an imaginary service called Bercel (Better Auth + Vercel lol):

  • Bob doesn't have a Bercel account
  • Bob wants to create an account with his work Google account
  • Bob clicks on "Sign in with Google" and selects his work account
  • Bob now has a new account (with his work Google account)

Bob doesn't use Bercel for a while, and now goes back to Bercel to log back into his existing account:

  • Bob clicks on "Sign in with Google" but selects his personal Google account
  • This now creates a new user and starts onboarding
  • Bob finishes onboarding and then signs out, and clicks on "Sign in with GitHub" instead
  • Process starts again

I say this because I have experienced this with a lot of people I know who "test" out different OAuth logins when trying to sign in, and these are less tech-savvy people who don't use password managers btw. It’s even happened to me a few times, as someone who does keep track of passwords/OAuth providers for all websites.

2 potential solutions

I see 2 good solutions for this, would be open to any others too:

  1. Have an option like { preventSignUps: true } on the signIn.social() client API - so if you sign in (as a new user) with an OAuth provider (that’s not linked to an existing account), it displays an error saying it doesn't exist / create an account instead
  2. Or have separate functions on the client API like signUp.social() and signIn.social() (like how it's for email)

Ideal workflow after one of these solutions

Ideally Bob should be able to:

  • Make an account using either email or OAuth (assume he started with email)
  • Link whatever OAuth providers he wants to this account (links his work Google and GitHub accounts)
  • And then be able to sign in to his main account using any of his linked OAuth accounts (email, work Google, or GitHub accounts)
  • Now if Bob tries to “Sign in with Apple”, he gets an error (and now Bob knows for sure he doesn’t have an account or linked this OAuth provider with his main account)
<!-- gh-comment-id:2564366318 --> @aryanprince commented on GitHub (Dec 28, 2024): ## Why I want this feature Imagine this scenario with an imaginary user Bob and an imaginary service called Bercel (Better Auth + Vercel lol): - Bob doesn't have a Bercel account - Bob wants to create an account with his work Google account - Bob clicks on "Sign in with Google" and selects his work account - Bob now has a new account (with his work Google account) Bob doesn't use Bercel for a while, and now goes back to Bercel to log back into his existing account: - Bob clicks on "Sign in with Google" but selects his personal Google account - This now creates a new user and starts onboarding - Bob finishes onboarding and then signs out, and clicks on "Sign in with GitHub" instead - Process starts again I say this because I have experienced this with a lot of people I know who "test" out different OAuth logins when trying to sign in, and these are less tech-savvy people who don't use password managers btw. It’s even happened to me a few times, as someone who does keep track of passwords/OAuth providers for all websites. ## 2 potential solutions I see 2 good solutions for this, would be open to any others too: 1. Have an option like `{ preventSignUps: true }` on the `signIn.social()` client API - so if you sign in (as a new user) with an OAuth provider (that’s not linked to an existing account), it displays an error saying it doesn't exist / create an account instead 2. Or have separate functions on the client API like `signUp.social()` and `signIn.social()` (like how it's for email) ## Ideal workflow after one of these solutions Ideally Bob should be able to: - Make an account using either email or OAuth (assume he started with email) - Link whatever OAuth providers he wants to this account (links his work Google and GitHub accounts) - And then be able to sign in to his main account using any of his linked OAuth accounts (email, work Google, or GitHub accounts) - Now if Bob tries to “Sign in with Apple”, he gets an error (and now Bob knows for sure he doesn’t have an account or linked this OAuth provider with his main account)
Author
Owner

@kzlar commented on GitHub (Jan 7, 2025):

Why I want this feature

Imagine this scenario with an imaginary user Bob and an imaginary service called Bercel (Better Auth + Vercel lol):

  • Bob doesn't have a Bercel account
  • Bob wants to create an account with his work Google account
  • Bob clicks on "Sign in with Google" and selects his work account
  • Bob now has a new account (with his work Google account)

Bob doesn't use Bercel for a while, and now goes back to Bercel to log back into his existing account:

  • Bob clicks on "Sign in with Google" but selects his personal Google account
  • This now creates a new user and starts onboarding
  • Bob finishes onboarding and then signs out, and clicks on "Sign in with GitHub" instead
  • Process starts again

I say this because I have experienced this with a lot of people I know who "test" out different OAuth logins when trying to sign in, and these are less tech-savvy people who don't use password managers btw. It’s even happened to me a few times, as someone who does keep track of passwords/OAuth providers for all websites.

100% this happens ALL the time. Very much a needed feature.

<!-- gh-comment-id:2574355616 --> @kzlar commented on GitHub (Jan 7, 2025): > ## Why I want this feature > Imagine this scenario with an imaginary user Bob and an imaginary service called Bercel (Better Auth + Vercel lol): > > * Bob doesn't have a Bercel account > * Bob wants to create an account with his work Google account > * Bob clicks on "Sign in with Google" and selects his work account > * Bob now has a new account (with his work Google account) > > Bob doesn't use Bercel for a while, and now goes back to Bercel to log back into his existing account: > > * Bob clicks on "Sign in with Google" but selects his personal Google account > * This now creates a new user and starts onboarding > * Bob finishes onboarding and then signs out, and clicks on "Sign in with GitHub" instead > * Process starts again > > I say this because I have experienced this with a lot of people I know who "test" out different OAuth logins when trying to sign in, and these are less tech-savvy people who don't use password managers btw. It’s even happened to me a few times, as someone who does keep track of passwords/OAuth providers for all websites. > 100% this happens ALL the time. Very much a needed feature.
Author
Owner

@hiaaryan commented on GitHub (Jan 16, 2025):

I'd + on this issue since in my organization, we'd use Microsoft Entra ID and only invited users can login. But I could not explicitly use this because it signs up a user even if they are not in the organization.

<!-- gh-comment-id:2594656343 --> @hiaaryan commented on GitHub (Jan 16, 2025): I'd + on this issue since in my organization, we'd use Microsoft Entra ID and only invited users can login. But I could not explicitly use this because it signs up a user even if they are not in the organization.
Author
Owner

@matthiasfeist commented on GitHub (Jan 29, 2025):

I'd + on this issue since in my organization, we'd use Microsoft Entra ID and only invited users can login. But I could not explicitly use this because it signs up a user even if they are not in the organization.

specific question to @hiaaryan: does the SSO plugin solve this issue? I am asking because I'm also looking into implementing Entra ID in the future...

<!-- gh-comment-id:2621472137 --> @matthiasfeist commented on GitHub (Jan 29, 2025): > I'd + on this issue since in my organization, we'd use Microsoft Entra ID and only invited users can login. But I could not explicitly use this because it signs up a user even if they are not in the organization. specific question to @hiaaryan: does the [SSO plugin](https://www.better-auth.com/docs/plugins/sso) solve this issue? I am asking because I'm also looking into implementing Entra ID in the future...
Author
Owner

@tehnrd commented on GitHub (Apr 16, 2025):

I recently encountered a similar use case, and here is how I handled it.

Let the users sign-in immediately, don't add friction, and do not stop them. You want to get their contact information as soon as possible for email campaigns, etc. In the callbackURL, send them to a signinhandler page.

const response = await authClient.signIn.oauth2({
	providerId: 'oauthappname',
	callbackURL: '/signinhandler?redirect=' + encodeURIComponent('/home'); 
});

The signinhandler page will go through the following logic tree:

I use the organizations plugin, so what I do on this page is:

  1. Check to see if the user has any organization invites, if yes, allow them to accept and proceed to one of the orgs with set active org call.

  2. If they have no invites and are not part of an org, prompt them for typical signup info to create an org and set them to active in this org.

  3. If they have no invites and are part of an org, I track last active org and set this as the active org.

Once all of this is done redirect them back to to /home or whatever the redirect param is.

If not using Organizations, you can have fields on the user to track if signup actions are complete and prompt the user accordingly.

<!-- gh-comment-id:2809527284 --> @tehnrd commented on GitHub (Apr 16, 2025): I recently encountered a similar use case, and here is how I handled it. Let the users sign-in immediately, don't add friction, and do not stop them. You want to get their contact information as soon as possible for email campaigns, etc. In the callbackURL, send them to a signinhandler page. ``` const response = await authClient.signIn.oauth2({ providerId: 'oauthappname', callbackURL: '/signinhandler?redirect=' + encodeURIComponent('/home'); }); ``` The `signinhandler` page will go through the following logic tree: I use the organizations plugin, so what I do on this page is: 1. Check to see if the user has any organization invites, if yes, allow them to accept and proceed to one of the orgs with set active org call. 2. If they have no invites and are not part of an org, prompt them for typical signup info to create an org and set them to active in this org. 3. If they have no invites and are part of an org, I track last active org and set this as the active org. Once all of this is done redirect them back to to `/home` or whatever the redirect param is. If not using Organizations, you can have fields on the user to track if signup actions are complete and prompt the user accordingly.
Author
Owner

@H7ioo commented on GitHub (Jun 29, 2025):

I've been following this discussion keenly and appreciate the insights shared, especially regarding the user experience challenges with OAuth sign-up and sign-in. The existing disableImplicitSignUp and requestSignUp options are valuable for controlling behavior within signIn.social().

However, the core problem remains: user expectations for clear "Sign Up" versus "Sign In" flows are often unmet by a single function handling both.

To provide a clearer API and a better user experience, I propose moving towards three distinct methods for social authentication:

1. authClient.signIn.social() (for Existing Users)

  • Purpose: Exclusively for logging in existing users.
  • Behavior: It attempts to log in an existing user via the OAuth provider.
  • New User Handling: If socialProviders.providerName.disableImplicitSignUp is configured, a new user attempting to use this method would receive an error indicating no account exists, guiding them to the sign-up process. This provides a clear separation of concerns.
  • Parameters: Focus on parameters relevant to authentication and redirection like callbackURL, errorCallbackURL, provider, disableRedirect, idToken, scopes, loginHint. additionalUserFields would generally not be relevant here.

2. authClient.signUp.social() (for New Users)

  • Purpose: Explicitly for registering new users.
  • Behavior: It attempts to create a new user account.
  • Existing User Handling: If an existing user attempts to signUp.social():
    • By default, it could log the user in without creating a new account or updating existing user fields (unless overrideUserInfoOnSignIn is specifically enabled for the provider). This matches the common expectation that signing up with an existing account logs you in.
    • Alternatively, if a socialProviders.providerName.disableSignIn option were introduced (similar to disableSignUp for the inverse scenario), it could throw an error, explicitly telling the user that an account already exists and they should use the sign-in flow.
  • Parameters: This method would naturally accept additionalUserFields for collecting user data during initial registration, along with callbackURL (for existing user redirection), newUserCallbackURL (for successful new sign-ups), errorCallbackURL, provider, etc.

We could also introduce a new unified method for "Login/Sign Up" and deprecate using signIn.social and signUp.social for other than their specific intents, making signIn.social ONLY for "sign in" and signUp.social ONLY for "sign up", with a unified method for both.

This three-method approach offers several benefits:

  • API Clarity: Explicitly defines the intent of each function call, aligning with common UI patterns.
  • Improved UX: Allows for distinct user flows, error messages, and onboarding experiences for new vs. returning users.
  • Explicit Onboarding: signUp.social() naturally supports collecting additionalUserFields as part of the new user flow.
  • Flexibility & Control: Provides precise control over behavior (e.g., preventing sign-ups on sign-in, or errors on sign-up if an account exists), without breaking existing implementations.
  • Future-Proofing: Establishes a clear, extensible structure for social authentication.

What are your thoughts on this?

<!-- gh-comment-id:3016736795 --> @H7ioo commented on GitHub (Jun 29, 2025): I've been following this discussion keenly and appreciate the insights shared, especially regarding the user experience challenges with OAuth sign-up and sign-in. The existing `disableImplicitSignUp` and `requestSignUp` options are valuable for controlling behavior within `signIn.social()`. However, the core problem remains: user expectations for clear "Sign Up" versus "Sign In" flows are often unmet by a single function handling both. To provide a clearer API and a better user experience, I propose moving towards **three distinct methods** for social authentication: **1. `authClient.signIn.social()` (for Existing Users)** * **Purpose:** Exclusively for **logging in existing users**. * **Behavior:** It attempts to log in an existing user via the OAuth provider. * **New User Handling:** If `socialProviders.providerName.disableImplicitSignUp` is configured, a new user attempting to use this method would receive an error indicating no account exists, guiding them to the sign-up process. This provides a clear separation of concerns. * **Parameters:** Focus on parameters relevant to authentication and redirection like `callbackURL`, `errorCallbackURL`, `provider`, `disableRedirect`, `idToken`, `scopes`, `loginHint`. `additionalUserFields` would generally not be relevant here. **2. `authClient.signUp.social()` (for New Users)** * **Purpose:** Explicitly for **registering new users**. * **Behavior:** It attempts to create a new user account. * **Existing User Handling:** If an existing user attempts to `signUp.social()`: * By default, it could **log the user in without creating a new account or updating existing user fields** (unless `overrideUserInfoOnSignIn` is specifically enabled for the provider). This matches the common expectation that signing up with an existing account logs you in. * Alternatively, if a `socialProviders.providerName.disableSignIn` option were introduced (similar to `disableSignUp` for the inverse scenario), it could throw an error, explicitly telling the user that an account already exists and they should use the sign-in flow. * **Parameters:** This method would naturally accept `additionalUserFields` for collecting user data during initial registration, along with `callbackURL` (for existing user redirection), `newUserCallbackURL` (for successful new sign-ups), `errorCallbackURL`, `provider`, etc. We could also introduce a new unified method for "Login/Sign Up" and deprecate using `signIn.social` and `signUp.social` for other than their specific intents, making `signIn.social` ONLY for "sign in" and `signUp.social` ONLY for "sign up", with a unified method for both. This three-method approach offers several benefits: * **API Clarity:** Explicitly defines the intent of each function call, aligning with common UI patterns. * **Improved UX:** Allows for distinct user flows, error messages, and onboarding experiences for new vs. returning users. * **Explicit Onboarding:** `signUp.social()` naturally supports collecting `additionalUserFields` as part of the new user flow. * **Flexibility & Control:** Provides precise control over behavior (e.g., preventing sign-ups on sign-in, or errors on sign-up if an account exists), without breaking existing implementations. * **Future-Proofing:** Establishes a clear, extensible structure for social authentication. What are your thoughts on this?
Author
Owner

@H7ioo commented on GitHub (Jun 29, 2025):

@ping-maxwell, @Bekacru, I'm willing to work on this feature. I would appreciate your opinions.

<!-- gh-comment-id:3017012834 --> @H7ioo commented on GitHub (Jun 29, 2025): @ping-maxwell, @Bekacru, I'm willing to work on this feature. I would appreciate your opinions.
Author
Owner

@edwardshturman commented on GitHub (Jul 3, 2025):

+1 on this — I want to be able to do something like verify the email the user signed in with through OAuth is on a whitelist, and based on that, either reject (not creating a new user), or proceed with sign-up

<!-- gh-comment-id:3033933906 --> @edwardshturman commented on GitHub (Jul 3, 2025): +1 on this — I want to be able to do something like verify the email the user signed in with through OAuth is on a whitelist, and based on that, either reject (not creating a new user), or proceed with sign-up
Author
Owner

@shreeram312 commented on GitHub (Jul 19, 2025):

I'm using BetterAuth with Google OAuth login, and I’d like to trigger some post-signup workflows — but only for first-time users. These include:
Pushing new users to a third-party job queue (e.g. Upstash Workflow)

Sending a welcome email on first signup which needs DB Lookup or is there any way ...?

<!-- gh-comment-id:3092499388 --> @shreeram312 commented on GitHub (Jul 19, 2025): I'm using BetterAuth with Google OAuth login, and I’d like to trigger some post-signup workflows — but only for first-time users. These include: Pushing new users to a third-party job queue (e.g. Upstash Workflow) Sending a welcome email on first signup which needs DB Lookup or is there any way ...?
Author
Owner

@daveycodez commented on GitHub (Jul 19, 2025):

You want to do this server side using a database hook

<!-- gh-comment-id:3092535464 --> @daveycodez commented on GitHub (Jul 19, 2025): You want to do this server side using a database hook
Author
Owner

@phsd0 commented on GitHub (Oct 1, 2025):

It also relates to account linking. Right now it is useless action to unlink lets say google acount because next time you sign in with google it links back. Am I understand it right?

<!-- gh-comment-id:3354328165 --> @phsd0 commented on GitHub (Oct 1, 2025): It also relates to account linking. Right now it is useless action to unlink lets say google acount because next time you sign in with google it links back. Am I understand it right?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#25877