Guides
Build guide

How to add social login in Next.js

Google, GitHub, Microsoft and LinkedIn sign-in with no OAuth routes to write. What is left is a decision: what one person becomes when they arrive through two providers.

Dharmendra Jagodana13 min read

In short

Social login on BuildBase needs no new Next.js code: create an OAuth app at the provider, add it as a login method, register the callback URI the method shows, and tick it on your auth client. Accounts then link by email address, but only when the provider vouches that the address is verified.

Every social login tutorial for Next.js spends its length on plumbing: the callback route, the token exchange, the provider config, the environment variables that have to match. On BuildBase you write none of it. Your app keeps calling the signIn() it already calls, and the hosted sign-in page grows a Google button.

What they skip is the part that bites a month later. Someone signed up with a password in March. In April they click Google. Same person?

Auth.js, which several of those tutorials build on, says no until you tell it otherwise. It refuses with OAuthAccountNotLinked, and the provider option that changes its mind is called allowDangerousEmailAccountLinking. BuildBase says yes, on one condition. The setup below is short; most of this guide is about that condition, because it decides which providers are worth offering at all.

Before you start

A Next.js app already signing in through BuildBase, wired up as in the Next.js setup guide, with at least one method working, so you can tell a provider problem from a wiring problem. You also need admin access to each provider's developer console: Google Cloud, GitHub, Microsoft Entra or the LinkedIn Developer Portal.

What you are building

A Google button on your hosted sign-in screen, plus GitHub, Microsoft or LinkedIn if you want them, with no new code in your Next.js app. When it works, a user clicks the button, approves at the provider and lands back in your app signed in. If their address already has an account and the provider vouches for it, they land in that account rather than a fresh one.

Those four are the ones this guide sets up. BuildBase also has Sign in with Apple and one OpenID Connect provider of your own, each a login method with a few fields of its own, alongside email and password, magic link and passkeys. There is no Facebook or X sign-in, so if your audience expects either, this is not the guide that gets you there.

Step one: create the OAuth app at the provider

Each provider hands you a client ID and a client secret. BuildBase needs both before it will show you the callback URI, so the provider app comes first and its redirect URI waits until step three.

  • Google. In the Google Auth Platform, open Clients, click Create client and choose Web application. Leave the redirect URIs empty for now.
  • GitHub. Settings → Developer settings → OAuth apps → New OAuth App. GitHub asks for an Authorization callback URL when you register the app, so put your homepage there as a placeholder. Then create a client secret on the app's page.
  • Microsoft. In the Microsoft Entra admin center, Entra ID → App registrations → New registration. Note what you pick under Supported account types, because step two has to agree with it. Copy the Application (client) ID, then go to Certificates & secrets → Client secrets → New client secret and copy the secret's Value. Entra never shows it again.
  • LinkedIn. Create an app in the LinkedIn Developer Portal and request Sign in with LinkedIn using OpenID Connect on its Products tab. The client ID and secret are on the Auth tab.

What you should see: a client ID and a secret for each provider you chose, and no redirect URI registered anywhere except GitHub's placeholder.

Step two: add a login method in BuildBase

In the console, open User Management → Authentication, go to the Methods tab and click Add Auth Method. Pick the provider, paste the client ID and secret, and save. Microsoft also asks for a tenant: common for work and personal accounts, organizations for work and school accounts only, or your own tenant ID. Make it match the account types you chose in Entra.

The console does not ask about scopes. Each method requests the email address and a basic profile on its own, which is all sign-in needs.

Saving shows a configuration card with a Callback / Redirect URI built from your organization and this method:

{serverUrl}/api/v1/auth/social/google/{orgId}/{methodId}/callback

Copy it off the card rather than typing it. Every character counts in a redirect check, and the card exists so you never assemble the URI by hand. Edit the method later and the card is there again.

What you should see: the new method in the Methods list, and a callback URI containing your org ID and a method ID.

Step three: register the callback URI at the provider

Paste the card's value into the provider app from step one.

  • Google. Add it under Authorized redirect URIs. For Google, the BuildBase card also shows an Authorized JavaScript Origin; that goes in the field of the same name. Google says changes to these settings can take 5 minutes to a few hours to apply, so a redirect_uri_mismatch straight after saving can just be Google catching up.
  • GitHub. Replace the placeholder callback URL. Replace it, do not add the new one under it: BuildBase does not send GitHub a redirect_uri, and when that parameter is missing GitHub sends the user to the first callback URL on the app. GitHub now allows up to 10, which makes it easy to leave the wrong one in first place.
  • Microsoft. Authentication → Add Redirect URI, choose Web, paste.
  • LinkedIn. On the Auth tab, add it to the authorized redirect URLs.

Notice what is missing from all four: your Next.js URL. The provider sends the user back to BuildBase, and BuildBase sends them on to your app through the redirect URLs registered on your auth client. So localhost, a preview deploy and production all share the one callback URI you just pasted. The Next.js-side causes of redirect_uri_mismatch that fill the forums - a wrong base-URL variable, a trailing slash, http behind a proxy - do not apply, because your app is not in the URI.

Step four: turn the method on for your auth client

A login method is an organization-wide catalogue entry. The hosted page only offers the methods ticked on the auth client your app signs in through. Open Authentication → Settings, edit the client whose ID is in your SaaSOSProvider, tick the new method under Methods, and save.

Skip this and everything looks configured while the button never appears.

Step five: sign in from your app

There is no new code. If your app already has a sign-in button, it already offers Google:

'use client';

import { useSaaSAuth } from '@buildbase/sdk/react';

export default function SignInButton() {
  const { signIn, isLoading, isAuthenticated } = useSaaSAuth();

  if (isLoading || isAuthenticated) return null;

  return <button onClick={() => signIn('/dashboard')}>Sign in</button>;
}

signIn() takes a return URL and nothing else. There is no signInWithGoogle(), and the SDK has no way to send someone straight to one provider from your own page; the user picks on the hosted screen. If you wanted a row of provider logos inside your app, that is what it costs.

What you should see: in a private window, click sign in and the hosted page shows a Google button. Approve at Google and you are back on /dashboard with isAuthenticated true. Two screens can appear in between, and both are expected: Trust this device? when your organization asks about devices, and a passkey offer when passkeys are on for the client and the user has none yet.

When BuildBase refuses a social sign-in - an unverified address, closed sign-up, a blocked user - the message appears on that hosted page, not in your Next.js app. There is no error for your code to catch.

The decision: one person, two providers

BuildBase matches a social sign-in to an account by the provider's own user ID first, then by email address, and links by email only when the provider vouches that the address is verified. In code, the provider's answer has to be exactly email_verified === true.

The address the provider returnsProvider says verifiedProvider does not
Already has an accountSigns into that accountRefused: "This email is already registered. Sign in the way you did before, then connect this provider from your account's security settings."
Has no account yetNew account, email verifiedNew account, and the hosted page asks for a 6-digit code first

Why refuse rather than link? Because the alternative is a takeover. If an unverified address could link, anyone who got a provider to put your user's address on an account they control would walk into your user's account with it.

This is the same line Auth.js draws, set at a different default. Its docs say automatic linking "is not secure between arbitrary providers and is disabled by default", and suggest turning it on only when you trust the provider has verified the address. BuildBase makes that call on every sign-in, from the provider's own claim, so there is no flag with "dangerous" in its name for you to flip.

Three consequences worth knowing before your users find them:

  • Linking adds a door, it does not remove one. A password user who signs in with Google once can still sign in with the password.
  • Two accounts stay two accounts. A signed-in user can connect a provider that carries a different address from the Security screen of your app's settings, but nothing merges two accounts that already exist.
  • The match is on the provider's account ID once it is linked. Change the address at Google, or in your app, and the next Google sign-in still finds the same account.

Key takeaway

A first social sign-in links by verified email and nothing else. So the question to ask of a provider is not whether your users have an account there, but which address that account will hand you.

Where "verified" comes from

ProviderCounts as verified when
Googlethe ID token's email_verified is true
GitHubthe address is the public profile email or, failing that, the primary address GitHub marks verified
Microsoftit is a personal account, or a work or school account whose ID token carries xms_edov
LinkedInthe ID token's email_verified is true

A GitHub user with neither a public email nor a verified primary one is turned away, since there is no verified address to find or create an account with. LinkedIn is the soft one: its own docs list email and email_verified as optional, and say its sign-in "does not verify user identities and should not be marketed as such".

The Microsoft work-account trap

Microsoft's own claims reference says the email claim "isn't guaranteed to be correct, and is mutable over time". So for work and school accounts BuildBase does not take the address on its word. It looks for xms_edov, which Microsoft describes as whether "the user's email domain owner has been verified": true when the address is on a domain the user's tenant has verified.

Entra does not put that claim in the ID token unless you ask. Leave it out and testing with a fresh account looks fine, apart from a verification-code screen you will probably wave through. Then a customer who signed up with a password tries Microsoft with the same work address, and gets refused.

The fix is one screen in your app registration: Token configuration → Add optional claim, token type ID, tick email and xms_edov, and Add. Personal Microsoft accounts count as verified without it.

Which providers to offer

Offer the providers that hand you the address your users already use with you. Every extra button is one more way for one person to become two accounts.

B2B tools: Google and Microsoft. A work address on Google Workspace comes back from Google, one on Microsoft 365 comes back from Microsoft, and either one matches the work address an invitation was sent to. Add the xms_edov claim before launch and these two link cleanly.

Developer tools: GitHub, with your eyes open. It is the button developers expect, but it returns the account's public profile email, or failing that its primary one. If that is a personal address and the invitation went to a work one, the sign-in belongs to the personal address: it creates a second account, or is refused if sign-up is closed, and neither sees the team's workspaces. That failure has its own guide, because from the outside it looks like the invitation never worked.

LinkedIn: only if your users already live there. Recruiting and sales tools, mostly. By its own documentation it is the weakest of the four at proving an address, and whenever it leaves out email_verified, a user who already has an account is refused rather than linked.

If what you want is sign-in without a password and without the two-accounts question, magic link proves the address directly, because the link is sent to it.

The failure modes

The button never shows. Either the method is not ticked on the auth client from step four, or two methods for the same provider are ticked. The hosted page shows one button per provider, built from the first one it finds, so the method you just fixed may not be the one users are clicking.

redirect_uri_mismatch, or the provider's version of it. Whatever is registered at the provider is not what the card shows. Nothing in your Next.js app moves the card's value, but recreating the method does: delete it and add it again, and the new method gets a new ID and so a new callback URI. Deleting also detaches the method from every client, so you would redo step four as well. When only the secret changed, edit the method instead. Leaving the secret blank on edit keeps the stored one.

GitHub drops users on your homepage with a code in the URL. The placeholder callback URL from step one is still first in the list.

Microsoft sign-in stops working one morning. The client secret expired. Entra caps client secrets at 24 months and recommends under 12, so put the expiry date in the calendar of whoever owns the console, and paste the new value into the method before it lapses.

A bare "Unauthorized" instead of your sign-in page. BuildBase holds the state for a social sign-in for 10 minutes and accepts it once. A user who leaves the consent screen open longer than that, or replays the callback with the back button, gets a 401 from the API and has to start again.

New users refused while existing ones sign in fine. Public sign-up is closed. Social sign-in obeys that setting: existing accounts get in, new ones are refused unless they started from an invitation link, as the authentication docs describe. If sign-up is open and new users still see "Signup is not enabled for this organization", check your plan's end-user limit. A social sign-up counts against it exactly as a password registration does.

Sessions behave the same whichever button someone clicked: 30 days by default. If you turn on passkeys as well, social login becomes the on-ramp: after a social sign-in, the hosted pages offer a passkey to anyone whose browser supports one and who has none yet. And if the redirect comes back and bounces you straight to sign-in again, that is the middleware redirect loop, not a provider problem.

Install

npm i @buildbase/sdk
auth
nextjs
social-login

Frequently Asked Questions

Do I need different Next.js code for Google, GitHub or Microsoft login?

No. Each provider is a login method you add in the BuildBase console and tick on your auth client, and the hosted sign-in page shows a button for each provider that client offers. Your app calls the same signIn() it uses for any other method, and signIn() takes only a return URL, so there is no per-provider call to make.

What happens when someone signs in with Google using an email that already has an account?

They land in that account, as long as the provider says the address is verified. If it does not, BuildBase refuses rather than linking and tells them to sign in the way they did before. A provider with a different address can be connected from the Security screen in your app, but nothing merges two existing accounts.

Why does a Microsoft work account get "This email is already registered"?

For work and school accounts, BuildBase treats the address as verified when the ID token carries the xms_edov claim (or an email_verified claim, which Entra does not normally send), and Entra adds xms_edov only when you ask for it. In your app registration, open Token configuration, add the email and xms_edov optional claims to the ID token, and sign in again.

Why do I get redirect_uri_mismatch?

The provider refuses any redirect URI that does not match one you registered with it. On BuildBase that value is the Callback / Redirect URI shown on the login method, not a URL in your Next.js app, so nothing you change in Next.js moves it; deleting the method and creating a new one does. On GitHub it also has to be first in the list.

Ship it

Create a project and run this guide against your own workspace.

7-day free trialNo credit card requiredCancel anytime