This tutorial guides you through the process of integrating Google and GitHub OAuth into your Next.js 14 project using Supabase. If you’ve ever struggled with managing multiple passwords for different websites, you understand the importance of simplifying the sign-in process.

By incorporating OAuth sign-in options into our application, users can effortlessly access our platform without the need to remember passwords. Implementing OAuth from scratch can be challenging, but with Supabase, the backend server has already implemented it using recommended best practices.

Our focus in this tutorial will be on configuring OAuth applications on both Google and GitHub, setting up the providers on Supabase, and implementing the OAuth feature in our Next.js 14 application.

The tutorial’s source code is available on GitHub: https://github.com/wpcodevo/nextjs14-supabase-ssr-authentication

More practice:

Setup Google and GitHub OAuth with Supabase in Next.js 14

How to Run the Next.js Application on Your Machine

Follow these steps to run the application on your machine:

  1. Begin by either downloading or cloning the project from its GitHub repository at https://github.com/wpcodevo/nextjs14-supabase-ssr-authentication. Open the source code in your preferred text editor.
  2. Duplicate the example.env file and rename the copy to .env. Retrieve both the Supabase URL and Anon key from your Supabase project, and then insert them into the respective variables within the .env file. If you’re uncertain about where to find these credentials, refer to the ‘Create a Project on Supabase’ section for guidance.
  3. Access the integrated terminal of your IDE and execute the command pnpm install to install all the necessary dependencies.
  4. Start the Next.js development server by running the command pnpm dev. Once the server is operational, access the application in your browser to interact with the authentication flow.
  5. To enable the Google and GitHub sign-in options, specific configurations are required in Supabase. For detailed instructions, refer to the guidance provided in the ‘Set up OAuth with Supabase in Next.js 14’ section if you are unfamiliar with the process.

Demo of the Google and GitHub OAuth Flow

In the Next.js 14 Supabase application, users can choose between two social login options: Google OAuth and GitHub OAuth. For this demonstration, I’ll showcase the GitHub OAuth process, as it closely resembles the steps for Google OAuth.

To log in using your GitHub account, click on the ‘Login‘ link in the navigation bar, which will take you to the login page. On this page, you’ll see a form with email and password input fields, accompanied by a submission button. Below the submit button, there are also buttons for Google and GitHub OAuth. Click on the ‘CONTINUE WITH GITHUB‘ button. Supabase will then generate the GitHub OAuth consent screen and redirect you to it.

sign in page of the supabase auth with Next.js 14

On the consent screen, assuming you are already logged into your GitHub account, you will be prompted to grant access to our application. Click on the green authorize button. Once you’ve completed this step, Supabase will upsert your credentials in their database.

Following that, they will redirect you to the /auth/callback route on our server. This route will extract a code that Supabase appended to the redirect URL and utilize it to make another request to Supabase, exchanging it for a session. Consequently, you will be automatically logged into the application.

GitHub OAuth Authorization Screen for the NextAuth Project

Now that you’ve successfully signed into the application, you can proceed to access protected pages, such as the profile page, where your account information is displayed. To log out, simply click on the ‘Logout‘ link in the navigation bar.

profile page of the supabase next.js 14 authentication project

Create a Project on Supabase

I’ll assume you already have a Supabase project integrated with Next.js 14, and you want to add Google and GitHub OAuth to it. If you don’t have an existing project, you can follow the article ‘Implement Authentication with Supabase in Next.js 14‘. For those using an existing project, make sure to include your Supabase URL and Anon key in your .env file. If you’re unsure how to obtain them, follow these instructions:

  1. Visit the Supabase Website:
    Open your web browser and go to the Supabase website.
  2. Sign In:
    Log in using your GitHub account. If you’re new to Supabase, visit the Sign Up page, and choose “Continue with GitHub” to create an account.
  3. Create an Organization:
    After signing up, log in to Supabase. You’ll be directed to the dashboard. Click “New Organization” to create one. Enter your organization name (e.g., your company name) and click “Create organization.”
  4. Create a New Project:
    Click “New Project” and select the organization. Provide a project name and generate a secure password. Once done, click “Create new project”.
  5. Get the Project URL and Anon Key:
    Return to your Next.js project and create a .env file in the root directory. Copy the project URL and anon key from the Supabase project, then add them to the .env file as NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY. If you can’t locate them, click the Settings icon in the left sidebar. From the settings menu, select “API”, where you should find the Supabase URL and anon key.

.env


NEXT_PUBLIC_SUPABASE_ANON_KEY=
NEXT_PUBLIC_SUPABASE_URL=

Configure the Google and GitHub OAuth Providers on Supabase

Let’s take things to the next level and implement Google and GitHub OAuth in the authentication flow.

Generate the OAuth Credentials on GitHub

Follow these steps to register a GitHub OAuth app and obtain the Client ID and Secret for the Supabase project.

  1. Navigate to the “Authentication” section in the left sidebar of your Supabase project. Select ‘Providers’ from the authentication menu.
  2. Choose GitHub from the available providers to reveal a dropdown where you can configure the OAuth credentials. Enable it by toggling the radio button in the dropdown.
    the note of the callback url provided by supabase
    Take note of the Callback URL, as it will be needed to configure the OAuth app on GitHub.
  3. Head to GitHub and sign in to your account if not already done. Click on your profile picture at the top right and choose “Settings” from the dropdown menu, redirecting you to the profile page.
    click on the profile photo icon to display a dropdown
  4. Scroll down to locate the “Developer settings” section and click on it.click on the developer settings menu on the profile settings page
  5. On the Developer settings page, find “OAuth Apps” and select it. Under “OAuth Apps,” click the “New OAuth App” button.
  6. Complete the “Application name” and “Homepage URL” fields with the relevant information for your app. Copy the “Authorization callback URL” from the Supabase GitHub provider dropdown and paste it into the corresponding field. This URL is where GitHub redirects users after authorizing the app.
    setting up the oath credentials on GitHub for the Supabase auth
    Once done, click “Register application” to create the OAuth App.
  7. Congratulations, your application has been created! You’ll be redirected to the application details page, where you can access the “Client ID” and generate the “Client Secret” keys.

    To generate the OAuth client secret, click “Generate a new client secret.” GitHub will prompt you to confirm your identity before generating the client secret.

    the oauth client secret will be generated
  8. Now that the GitHub OAuth client secret is generated, add it along with the client ID on Supabase. Enter the Client ID and secret in the GitHub dropdown on Supabase and click ‘save’.
    enter the client id and secret provided by GitHub on Supabase

Generate the OAuth Credentials on Google

In this section, you will learn how to acquire the Google OAuth2 client ID and secret from the Google Cloud Console and use them to configure the Google OAuth provider on Supabase. Follow these steps:

  1. Navigate to the “Authentication” section in the left sidebar of your Supabase project and select ‘Providers’. Choose Google from the list of available providers, unveiling a dropdown where you can input the Google OAuth credentials.
    take note of the callback URL provided on Supabas Google OAuth provider
    Take note of the callback URL, as it will be necessary when setting up OAuth on the Google Cloud Console.
  2. Visit https://console.developers.google.com/ and ensure you are signed in to your Google account.
  3. Click the dropdown menu at the top of the page to reveal a pop-up window. From there, you can either select an existing project or create a new one.select a project or create a new one on the Google Cloud API dashboard
  4. To create a new project, click the “New Project” button in the top-right corner of the pop-up window. Provide a name for your project and click “Create” to finish the process.
    create a new project on the google console api dashboard
  5. Once your project is created, click “SELECT PROJECT” from the notifications.
    click on the newly created project from the notification
  6. Access the “OAuth consent screen” in the left sidebar. Choose “External” as the “User Type” and click “CREATE”.
    select external under the user type and click on create
  7. On the “Edit app registration” screen, complete the necessary details in the “App information” section, including a logo for the consent screen.
    provide the consent screen credentials part 1
    Under “App domain,” provide links to your homepage, privacy policy, and terms of service pages. Add your email address in the “Developer contact information” section and click “SAVE AND CONTINUE”.
    provide the consent screen credentials part 2
  8. On the “Scopes” screen, click “ADD OR REMOVE SCOPES,” select .../auth/userinfo.email and .../auth/userinfo.profile, and click “UPDATE.” Scroll down and click “SAVE AND CONTINUE”.
    select the scopes
  9. On the “Test users” screen, include the email addresses of Google accounts authorized to test your app in sandbox mode. Click “ADD USERS”, input the email addresses, and click “SAVE AND CONTINUE”.add the test user
  10. Navigate to “Credentials” in the left sidebar. Click “CREATE CREDENTIALS” and choose “OAuth client ID”.
    select oauth client ID
  11. Select “Web application” as the application type, provide a name for your app, and input the authorized callback URI from the Google OAuth provider dropdown on Supabase. Click “Create” once done.
    Paste the callback URL provided by Supabase on the Google Cloud Console

    Once the client ID is generated, copy both the client ID and secret from the “Credentials” page. Paste them into the Google provider dropdown on Supabase and click ‘Save’ to complete the process.
    enter the client id and secret provided by Google Cloud Console

Create a Supabase Browser Client

Now that you have configured the Google and GitHub OAuth providers on Supabase, we can proceed to create a Supabase browser client that will enable us to integrate the OAuth sign-in options into our Next.js 14 application.

To set up the client, you first need to install the @supabase/ssr package if you haven’t already done so. Once installed, create a lib directory at the root level of your project. Inside the lib folder, create another folder named supabase. Then, within the supabase folder, create a client.ts file and add the following code:

lib/supabase/client.ts


import { useMemo } from 'react';
import { createBrowserClient } from '@supabase/ssr';

export function getSupabaseBrowserClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  );
}

function useSupabaseClient() {
  return useMemo(getSupabaseBrowserClient, []);
}

export default useSupabaseClient;

This client can only be used from within client components to interact with the Supabase backend for tasks such as authentication, database queries, and other Supabase-related operations. The useMemo hook ensures optimal performance and prevents unnecessary re-creation of the client during component re-renders.

Implement Google and GitHub OAuth

To integrate Google and GitHub OAuth providers into your Next.js 14 project using Supabase, open the client component where you render the OAuth buttons and create two functions: loginWithGitHub and loginWithGoogle. Below is an overview of both functions:

  1. loginWithGoogle – This function uses the supabase.auth.signInWithOAuth method and provides ‘google‘ as the provider. When invoked, it generates the Google OAuth consent URL and redirects the user to it.
  2. loginWithGitHub – This function is similar to the loginWithGoogle function, except that it uses ‘github‘ as the provider. When invoked, it generates the GitHub OAuth consent screen URL and redirects the user accordingly.

app/login/login-form.tsx


'use client';

import { LoginUserInput, loginUserSchema } from '@/lib/user-schema';
import { zodResolver } from '@hookform/resolvers/zod';
import Image from 'next/image';
import { useRouter } from 'next/navigation';
import { useState, useTransition } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { signInWithEmailAndPassword } from '../_actions';
import toast from 'react-hot-toast';
import useSupabaseClient from '@/lib/supabase/client';

export const LoginForm = () => {
  const router = useRouter();
  const [error, setError] = useState('');
  const [isPending, startTransition] = useTransition();
  const supabase = useSupabaseClient();

  const methods = useForm<LoginUserInput>({
    resolver: zodResolver(loginUserSchema),
  });

  const {
    reset,
    handleSubmit,
    register,
    formState: { errors },
  } = methods;

  const onSubmitHandler: SubmitHandler<LoginUserInput> = async (values) => {
    startTransition(async () => {
      const result = await signInWithEmailAndPassword(values);

      const { error } = JSON.parse(result);
      if (error?.message) {
        setError(error.message);
        toast.error(error.message);
        console.log('Error message', error.message);
        reset({ password: '' });
        return;
      }

      setError('');
      toast.success('successfully logged in');
      router.push('/');
    });
  };

  const loginWithGitHub = () => {
    supabase.auth.signInWithOAuth({
      provider: 'github',
      options: {
        redirectTo: `${location.origin}/auth/callback`,
      },
    });
  };

  const loginWithGoogle = () => {
    supabase.auth.signInWithOAuth({
      provider: 'google',
      options: {
        redirectTo: `${location.origin}/auth/callback`,
      },
    });
  };

  const input_style =
    'form-control block w-full px-4 py-5 text-sm font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none';

  return (
    <form onSubmit={handleSubmit(onSubmitHandler)}>
      {error && (
        <p className='text-center bg-red-300 py-4 mb-6 rounded'>{error}</p>
      )}
      <div className='mb-6'>
        <input
          type='email'
          {...register('email')}
          placeholder='Email address'
          className={`${input_style}`}
        />
        {errors['email'] && (
          <span className='text-red-500 text-xs pt-1 block'>
            {errors['email']?.message as string}
          </span>
        )}
      </div>
      <div className='mb-6'>
        <input
          type='password'
          {...register('password')}
          placeholder='Password'
          className={`${input_style}`}
        />
        {errors['password'] && (
          <span className='text-red-500 text-xs pt-1 block'>
            {errors['password']?.message as string}
          </span>
        )}
      </div>
      <button
        type='submit'
        style={{ backgroundColor: `${isPending ? '#ccc' : '#3446eb'}` }}
        className='inline-block px-7 py-4 bg-blue-600 text-white font-medium text-sm leading-snug uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out w-full'
        disabled={isPending}
      >
        {isPending ? 'loading...' : 'Sign In'}
      </button>

      <div className='flex items-center my-4 before:flex-1 before:border-t before:border-gray-300 before:mt-0.5 after:flex-1 after:border-t after:border-gray-300 after:mt-0.5'>
        <p className='text-center font-semibold mx-4 mb-0'>OR</p>
      </div>

      <a
        className='px-7 py-2 text-white font-medium text-sm leading-snug uppercase rounded shadow-md hover:shadow-lg focus:shadow-lg focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out w-full flex justify-center items-center mb-3'
        style={{ backgroundColor: '#3b5998' }}
        onClick={loginWithGoogle}
        role='button'
      >
        <Image
          className='pr-2'
          src='/images/google.svg'
          alt=''
          style={{ height: '2rem' }}
          width={35}
          height={35}
        />
        Continue with Google
      </a>
      <a
        className='px-7 py-2 text-white font-medium text-sm leading-snug uppercase rounded shadow-md hover:shadow-lg focus:shadow-lg focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out w-full flex justify-center items-center'
        style={{ backgroundColor: '#55acee' }}
        onClick={loginWithGitHub}
        role='button'
      >
        <Image
          className='pr-2'
          src='/images/github.svg'
          alt=''
          width={40}
          height={40}
        />
        Continue with GitHub
      </a>
    </form>
  );
};

The redirectTo option is where Supabase will redirect the user after the successful OAuth authentication. Supabase will also append a code to the URL, which our application can use to make another request to Supabase in exchange for the user’s session.

Create a Route to Exchange the OAuth Code for a Session

Now let’s move on to creating the route responsible for exchanging the code for the user’s session. As this route will run on the server, we need to create a Supabase server client to interact with the Supabase backend. To create the route, follow these steps:

  1. Navigate to the app directory and create a new folder named auth.
  2. Inside the auth folder, create a subdirectory called callback.
  3. Within the callback folder, create a route.ts file and add the following code.

app/auth/callback/route.ts


import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
import { type CookieOptions, createServerClient } from '@supabase/ssr';

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

  if (code) {
    const cookieStore = cookies();

    const supabase = createServerClient(
      process.env.NEXT_PUBLIC_SUPABASE_URL!,
      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
      {
        cookies: {
          get(name: string) {
            return cookieStore.get(name)?.value;
          },
          set(name: string, value: string, options: CookieOptions) {
            cookieStore.set({ name, value, ...options });
          },
          remove(name: string, options: CookieOptions) {
            cookieStore.delete({ name, ...options });
          },
        },
      }
    );
    const { error } = await supabase.auth.exchangeCodeForSession(code);
    if (!error) {
      return NextResponse.redirect(`${origin}${next}`);
    }
  }

  // return the user to an error page with instructions
  return NextResponse.redirect(`${origin}/auth/auth-code-error`);
}

In the above code, we extract the code from the URL search parameters. Next, we initialize a Supabase server client and utilize this client to make an HTTP request to the Supabase backend to exchange the code for a user session.

Create Middleware to Refresh Expired Cookies

Finally, let’s create a Next.js middleware that will refresh expired cookies even before the route gets loaded. Typically, when a user leaves their browser tab open and their cookies expire, attempting to access a protected route may prompt them to authenticate, even though they were previously logged in. This occurs because the request made to Supabase was based on old cookies that have expired.

Therefore, the Next.js middleware will allow us to refresh the cookies and attach them to the response body. To implement this, create a middleware.ts file and add the following code:

middleware.ts


import { createServerClient, type CookieOptions } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';

export async function middleware(request: NextRequest) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  });

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return request.cookies.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          request.cookies.set({
            name,
            value,
            ...options,
          });
          response = NextResponse.next({
            request: {
              headers: request.headers,
            },
          });
          response.cookies.set({
            name,
            value,
            ...options,
          });
        },
        remove(name: string, options: CookieOptions) {
          request.cookies.set({
            name,
            value: '',
            ...options,
          });
          response = NextResponse.next({
            request: {
              headers: request.headers,
            },
          });
          response.cookies.set({
            name,
            value: '',
            ...options,
          });
        },
      },
    }
  );

  await supabase.auth.getUser();

  return response;
}

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     * Feel free to modify this pattern to include more paths.
     */
    '/((?!_next/static|_next/image|favicon.ico).*)',
  ],
};

Conclusion

And there you have it! In this tutorial, you learned how to integrate Google and GitHub OAuth providers into your Next.js 14 application using Supabase. I hope you found this guide helpful and enjoyable. If you have any questions or feedback, feel free to leave them in the comment section. Thanks for reading!