Skip to content
CodeAndBuild LogoCodeAndBuild

Next.js

How to Add Social Logins (Google & Facebook) to Next.js App with Supabase

Turn on Google and Facebook sign-in with Supabase Auth, start the OAuth flow from the browser, and finish it in a Next.js callback route.

CodeAndBuild Team9 min read
  • Next.js
  • Supabase
  • Auth
  • OAuth
On this page
  1. Turn the providers on
  2. Environment variables
  3. Start the OAuth redirect
  4. Exchange the code for a session
  5. Login buttons
  6. Check the round trip

Supabase Auth already speaks OAuth. Your Next.js app starts the redirect, and a callback route trades the returned code for a session cookie. The examples below use the public anon key in the browser. Keep the service-role key on the server, and do not commit either value.

Turn the providers on

In the Supabase dashboard, open Authentication, then Providers. Enable Google and Facebook. Each provider asks for a client id and a client secret from that platform's developer console.

  1. Create a Google OAuth client and a Facebook app. Copy each client id and secret into the matching Supabase provider form.
  2. On Google and Facebook, set the allowed redirect to your project callback: https://<project-ref>.supabase.co/auth/v1/callback. That address belongs to Supabase, not to your Next.js app.
  3. In Supabase, under Authentication and URL configuration, add the addresses your app will return to: http://localhost:3000/auth/callback and https://your-domain.com/auth/callback.

Environment variables

Put the project URL and the anon key in .env.local. Both are safe to expose to the browser. The service-role key is not, so leave it out of any NEXT_PUBLIC_ variable.

.env.localtext
NEXT_PUBLIC_SUPABASE_URL=https://<project-ref>.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=<your-anon-key>

Start the OAuth redirect

signInWithOAuth sends the browser to Google or Facebook, then back to the redirectTo URL you pass. Use your own origin so local and production each return to the right callback.

lib/sign-in.tstsx
"use client";

import { createBrowserClient } from "@supabase/ssr";

const supabase = createBrowserClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);

type Provider = "google" | "facebook";

export async function signInWith(provider: Provider) {
  const redirectTo = `${window.location.origin}/auth/callback`;

  const { error } = await supabase.auth.signInWithOAuth({
    provider,
    options: { redirectTo },
  });

  if (error) throw error;
}

Exchange the code for a session

Supabase sends the user back to /auth/callback with a code query parameter. The route handler gives that code to exchangeCodeForSession, which writes the auth cookies. Only follow a next path that starts with /, so the parameter cannot redirect to another site.

app/auth/callback/route.tsts
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const { searchParams, origin } = new URL(request.url);
  const code = searchParams.get("code");
  const next = searchParams.get("next") ?? "/";
  const safeNext = next.startsWith("/") ? next : "/";

  if (code) {
    const cookieStore = await cookies();
    const supabase = createServerClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
      {
        cookies: {
          getAll() {
            return cookieStore.getAll();
          },
          setAll(cookiesToSet) {
            cookiesToSet.forEach(({ name, value, options }) => {
              cookieStore.set(name, value, options);
            });
          },
        },
      },
    );

    const { error } = await supabase.auth.exchangeCodeForSession(code);
    if (!error) {
      return NextResponse.redirect(`${origin}${safeNext}`);
    }
  }

  return NextResponse.redirect(`${origin}/login?error=auth`);
}

What the callback is doing

  • Read code from the query string. Without it, there is no session to create.
  • Build a server client that can read and write cookies through next/headers.
  • Call exchangeCodeForSession once. Supabase sets the access and refresh cookies.
  • Redirect into the app. If the exchange fails, send the user to the login page with an error flag.

Login buttons

The buttons only call signInWith. Tailwind handles the layout. Google stays on a light button so the label remains readable. Facebook uses its blue. Both are type="button" so they do not submit a surrounding form.

components/SocialLogin.tsxtsx
"use client";

import { signInWith } from "@/lib/sign-in";

export function SocialLogin() {
  return (
    <div className="mx-auto flex w-full max-w-sm flex-col gap-3 rounded-2xl border border-white/10 bg-zinc-950 p-6">
      <h2 className="text-lg font-semibold text-white">Continue</h2>
      <button
        type="button"
        onClick={() => signInWith("google")}
        className="rounded-xl bg-white px-4 py-2.5 text-sm font-medium text-zinc-950 transition-colors hover:bg-zinc-200"
      >
        Continue with Google
      </button>
      <button
        type="button"
        onClick={() => signInWith("facebook")}
        className="rounded-xl bg-[#1877F2] px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-[#1668d6]"
      >
        Continue with Facebook
      </button>
    </div>
  );
}

Check the round trip

  1. 01

    Install the client

    From the app, run npm install @supabase/supabase-js @supabase/ssr.

  2. 02

    Click a provider

    The browser should leave your site, land on Google or Facebook, and return to /auth/callback.

  3. 03

    Confirm the session

    After the redirect home, supabase.auth.getUser() on the server should return the signed-in user. If it does not, the callback URL is missing from the Supabase allow list, or the provider secret does not match.

More guides