In this comprehensive guide, you will learn how to set up Google and GitHub OAuth with NextAuth v5 in your Next.js 14 project. Incorporating OAuth sign-in options into your application can eliminate the need for users to sign in with their email and password. Consequently, they won’t have to remember the password used for your application, as many websites require users to use unique passwords.

With the release of NextAuth v5, a couple of breaking changes have been introduced, and getting NextAuth up and running in the latest release of Next.js, which is version 14, requires some setups.

In this article, I will guide you from scratch: from setting up the OAuth app on Google and GitHub, to configuring NextAuth with the providers, to modifying the JWT payload and session data to include custom information, to protecting routes using Next.js middleware with NextAuth.

The tutorial’s source code is available on GitHub: https://github.com/wpcodevo/nextauth-nextjs14-prisma

More practice:

Set up Google and GitHub OAuth with NextAuth in Next.js 14

How to Run the Project on Your Computer

To explore the Google and GitHub OAuth integration with NextAuth v5 on your machine, follow these steps to get the project up and running:

  1. Download or clone the project from its GitHub repository at https://github.com/wpcodevo/nextauth-nextjs14-prisma and open the source code in your preferred IDE or code editor.
  2. Open the integrated terminal in your IDE, and from the root directory, execute the command pnpm install to install all the necessary dependencies.
  3. Duplicate the example.env file and rename the copy to .env.
  4. Launch the PostgreSQL server with Docker using the command docker-compose up -d.
  5. Add your Google and GitHub OAuth client IDs and secrets to the respective variables in the .env file. If you don’t know how to obtain the OAuth client IDs and secrets, you can follow the instructions provided in the ‘Obtaining Google and GitHub OAuth Credentials’ section.
  6. Synchronize the Prisma migrations with the PostgreSQL database schema and generate the Prisma Client by running the command pnpm prisma migrate dev.
  7. Start the Next.js development server by running the command pnpm dev. Once the server is ready to accept requests, open the application in your browser and explore the features to understand the authentication flow

Demo of the OAuth Flow

Now that you’ve got the project running on your computer, let’s proceed to explore its features to understand the OAuth flow. Below is the default NextAuth sign-in form, but in this application, we are using a custom login form page instead. To access this page, you need to comment out the pages option in the NextAuth configurations inside the auth.ts file.

default signin form for nextauth v5 in next.js 14 having both the GitHub and Google OAuth button along with the form inputs

On the login form, you have three options to sign into the application: one using email and password, and the other two are social logins comprising Google and GitHub OAuth. For simplicity, I will demonstrate how Google OAuth works in our application, as the same process applies to the GitHub OAuth flow.

To sign in with your Google account, simply click on the ‘CONTINUE WITH GOOGLE‘ button. This action will cause NextAuth to generate the Google OAuth consent screen URL and redirect you to it.

sign in page of the NextAuth project with Next.js 14

On the Google OAuth Consent screen, you will be prompted to select an account to continue. Since it’s your first time signing into the application, you will be asked to authorize access to the application. Select ‘Yes’ to grant access.

Google OAuth2 Consent Screen

Once you’ve granted access to the Next.js app, you will be redirected back to the application page, and NextAuth will automatically sign you into the application. You can now access protected pages or routes like the /profile page where your account credentials are displayed.

access a protected page after the Google OAuth successfully signing in

Obtaining Google and GitHub OAuth Credentials

At this point, you have gained an overview of the OAuth flow. Let’s now delve deeper into the process by creating OAuth apps on both Google and GitHub. Then, utilize these credentials to configure the OAuth providers with NextAuth.

Google OAuth Client ID and Secret

In this section, you’ll learn how to obtain the Google OAuth2 client ID and secret from the Google Cloud Console. Here are the steps to follow:

  1. Visit https://console.developers.google.com/ and make sure you are logged into your Google account.
  2. Click the dropdown menu at the top of the page to display a pop-up window. From there, you can choose an existing project or create a new one.select a project or create a new one on the Google Cloud API dashboard
  3. To create a new project, click the “New Project” button in the top-right corner of the pop-up window. Enter a name for your project and click the “Create” button to complete the process.
    create a new project on the google console api dashboard
  4. Once your project is created, click the “SELECT PROJECT” button from the notifications.
    click on the newly created project from the notification
  5. Click the “OAuth consent screen” menu on the left sidebar. Choose “External” as the “User Type” and click on the “CREATE” button.
    select external under the user type and click on create
  6. On the “Edit app registration” screen, go to the “App information” section and fill in the required details, including a logo for the consent screen.
    provide the consent screen credentials part 1
    Under the “App domain” section, provide links to your homepage, privacy policy, and terms of service pages. Input your email address under the “Developer contact information” section and click the “SAVE AND CONTINUE” button.
    provide the consent screen credentials part 2
  7. On the “Scopes” screen, click on the “ADD OR REMOVE SCOPES” button, select .../auth/userinfo.email and .../auth/userinfo.profile from the list of options, and then click on the “UPDATE” button. Scroll down and click the “SAVE AND CONTINUE” button.
    select the scopes
  8. On the “Test users” screen, add the email addresses of Google accounts that will be authorized to test your application while it is in sandbox mode. Click the “ADD USERS” button and input the email addresses. Click the “SAVE AND CONTINUE” button to proceed.add the test user
  9. Click on the “Credentials” option in the left sidebar. Select the “CREATE CREDENTIALS” button and choose “OAuth client ID” from the list of options provided.
    select oauth client ID
  10. Choose “Web application” as the application type, and input a name for your app. Specify the authorized redirect URI as http://localhost:3000/api/auth/callback/google and click the “Create” button.
    Register the Google OAuth Client ID and Secret for NextAuth

    After the client ID has been created, copy the client ID and secret from the “Credentials” page and add them to the .env file. Here’s an example .env file to guide you.

.env


AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=

GitHub OAuth Client ID and Secret

Follow these steps to register an OAuth app on GitHub and obtain the Client ID and Secret.

  1. Start by signing into your GitHub account. Click on your profile picture at the top right corner and select “Settings” from the dropdown menu. You’ll be taken to the GitHub Developer Settings page.
    click on the profile photo icon to display a dropdown
  2. Scroll down to find the “Developer settings” section and click on it.
    click on the developer settings menu on the profile settings page
  3. On the Developer settings page, look for “OAuth Apps” and click on it. Under the “OAuth Apps” section, click on the “New OAuth App” button.
  4. Fill in the “Application name” and “Homepage URL” input fields with the appropriate name and URL for your app. For the “Authorization callback URL”, enter the URL where GitHub will redirect users after they’ve authorized the app.
    Register the GitHub OAuth App for NextAuth
    In this example, you need to enter http://localhost:3000/api/auth/callback/github as the redirect URL. Once you’re finished, hit the “Register application” button to create the OAuth App.
  5. You’ll now be taken 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 on the “Generate a new client secret” button. GitHub will then prompt you to confirm your identity before the client secret is generated.

    the oauth client secret will be generated
  6. With the GitHub OAuth client secret now generated, it’s time to add it, along with the client ID, to your .env file. Check out this example .env file to guide you.

.env


AUTH_GITHUB_ID=
AUTH_GITHUB_SECRET=

Initializing NextAuth.js in Next.js 14

Now that you’ve added the OAuth credentials to your .env file, let’s proceed to initialize NextAuth in the Next.js 14 project if you haven’t already done so. To accomplish this, begin by installing the NextAuth beta package and the Prisma adapter using the commands below:


pnpm add next-auth@beta @auth/prisma-adapter

# or
yarn add next-auth@beta @auth/prisma-adapter

# or
npm i next-auth@beta @auth/prisma-adapter

Next, in the root level of your project, create an auth.ts file and include the following code. This code sets up NextAuth and exports a handlers function that we can use to set up the Next.js HTTP routes.

auth.ts


import NextAuth from "next-auth";
import { PrismaAdapter } from "@auth/prisma-adapter";
import prisma from "./prisma/prisma";

export const { handlers, auth, signIn, signOut } = NextAuth({
  session: { strategy: "jwt" },
  adapter: PrismaAdapter(prisma),
  pages: {
    signIn: "/login",
  },
  providers: [
   
  ],
  callbacks: {
   
  },
});

To set up the Next.js HTTP routes for NextAuth, follow the steps below:

  1. Navigate to the app directory and create an api folder.
  2. Inside the api folder, create another directory named auth. Then, within the auth folder, create a dynamic route folder named [...nextauth].
  3. Finally, create a route.ts file in the [...nextauth] folder and add the code below:

app/api/auth/[…nextauth]/route.ts


import { handlers } from '@/auth';

export const { GET, POST } = handlers;

Next, include the AUTH_SECRET variable in the .env file, and utilize openssl rand -hex 32 to generate a unique string value for it. This secret will be used by NextAuth for signing and decoding the JSON Web Token.

.env


AUTH_SECRET=my_ultra_secure_nextauth_secret

Adding Google and GitHub Providers to NextAuth

At this point, we can now proceed to add the Google and GitHub OAuth providers to the NextAuth configurations. The process is simple: import the providers from the NextAuth package and include them in the providers array of the NextAuth config object. Here is an example.

auth.ts


import NextAuth from "next-auth";
import { PrismaAdapter } from "@auth/prisma-adapter";
import prisma from "./prisma/prisma";
import github from "next-auth/providers/github";
import google from "next-auth/providers/google";
import CredentialsProvider from "next-auth/providers/credentials";

export const { handlers, auth, signIn, signOut } = NextAuth({
  session: { strategy: "jwt" },
  adapter: PrismaAdapter(prisma),
  providers: [
    github,
    google,
    CredentialsProvider({}),
  ]
});

With that in place, when you visit the default NextAuth sign-in page at http://localhost:3000/api/auth/signin, you should see both the GitHub and Google OAuth sign-in options. You can click on either of them to sign into the application.

google and github oauth sign in option on the default nextauth signin page

Assuming you have a route for returning the session data, you’ll notice that the user’s ID isn’t included. From what I’ve read online, NextAuth purposely omitted this for security reasons. However, we will explore how to manually include the ID and other fields in the session data.

print the NextAuth session data from an API route

Here is the code for creating the API route that returns the session data in the JSON response:

app/api/session/route.ts


import { auth } from "@/auth";
import { NextResponse } from "next/server";

export async function GET(_request: Request) {
  const session = await auth();

  if (!session?.user) {
    return new NextResponse(
      JSON.stringify({ status: "fail", message: "You are not logged in" }),
      { status: 401 }
    );
  }

  return NextResponse.json({
    authenticated: !!session,
    session,
  });
}

Modifying the JWT Payload and Session Data

To modify the JSON Web Token payload and session data, NextAuth provides us with two handy callback functions named jwt and session. Using these two callbacks, we can add any necessary data to the session data and JWT payload that our application requires. Here is an example demonstrating how to modify the JWT payload and session data.

auth.ts


export const { handlers, auth, signIn, signOut } = NextAuth({
  session: { strategy: "jwt" },
  adapter: PrismaAdapter(prisma),
  providers: [github, google, CredentialsProvider({})],
  callbacks: {
    session: ({ session, token }) => {
      return {
        ...session,
        user: {
          ...session.user,
          id: token.id,
          randomKey: token.randomKey,
        },
      };
    },
    jwt: ({ token, user }) => {
      if (user) {
        const u = user as unknown as any;
        return {
          ...token,
          id: u.id,
          randomKey: u.randomKey,
        };
      }
      return token;
    },
  },
});

When you revisit the route, you should now see the ID included in the session object, along with any other data you added to the session.

Viewing the session data from an API endpoint

Implementing Google and GitHub OAuth Sign-in Options

Let’s proceed to add the OAuth buttons to our sign-in form that users can click to initiate the OAuth sign-in process. The process is quite simple, as NextAuth provides a signIn function that we include on our button, with the provider type provided as the first argument.

For Google OAuth, you need to provide google as the first argument, and for GitHub OAuth, you need to provide github. Here is a simple example that uses the Google and GitHub OAuth providers.

app/login/login-form.tsx


'use client';

import Image from 'next/image';
import { useSearchParams } from 'next/navigation';
import { signIn } from 'next-auth/react';

export const LoginForm = () => {

  const searchParams = useSearchParams();
  const callbackUrl = searchParams.get('callbackUrl') || '/profile';

  return (
    <form>
      <a
        className=''
        style={{ }}
        onClick={() => signIn('google', { callbackUrl })}
        role='button'
      >
        <Image
          className='pr-2'
          src='/images/google.svg'
          alt=''
          style={{ height: '2rem' }}
          width={35}
          height={35}
        />
        Continue with Google
      </a>
      <a
        className=''
        style={{ }}
        onClick={() => signIn('github', { callbackUrl })}
        role='button'
      >
        <Image
          className='pr-2'
          src='/images/github.svg'
          alt=''
          width={40}
          height={40}
        />
        Continue with GitHub
      </a>
    </form>
  );
};

You can refer to the code below to see how to implement the credentials provider along with both the Google and GitHub OAuth providers.

app/login/login-form.tsx


'use client';

import { zodResolver } from '@hookform/resolvers/zod';
import Image from 'next/image';
import { useRouter, useSearchParams } from 'next/navigation';
import { useState } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import { signIn } from 'next-auth/react';
import { LoginUserInput, loginUserSchema } from '@/lib/user-schema';



export const LoginForm = () => {
  const router = useRouter();
  const [error, setError] = useState('');
  const [submitting, setSubmitting] = useState(false);

  const searchParams = useSearchParams();
  const callbackUrl = searchParams.get('callbackUrl') || '/profile';

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

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

  const onSubmitHandler: SubmitHandler<LoginUserInput> = async (values) => {
    try {
      setSubmitting(true);

      const res = await signIn('credentials', {
        redirect: false,
        email: values.email,
        password: values.password,
        redirectTo: callbackUrl,
      });

      setSubmitting(false);

      if (!res?.error) {
        toast.success('successfully logged in');
        router.push(callbackUrl);
      } else {
        reset({ password: '' });
        const message = 'invalid email or password';
        toast.error(message);
        setError(message);
      }
    } catch (error: any) {
      toast.error(error.message);
      setError(error.message);
    } finally {
      setSubmitting(false);
    }
  };

  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: `${submitting ? '#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={submitting}
      >
        {submitting ? '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={() => signIn('google', { callbackUrl })}
        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={() => signIn('github', { callbackUrl })}
        role='button'
      >
        <Image
          className='pr-2'
          src='/images/github.svg'
          alt=''
          width={40}
          height={40}
        />
        Continue with GitHub
      </a>
    </form>
  );
};

Protecting Routes with NextAuth Middleware

Next, let’s explore how to secure API routes or pages with Next.js middleware along with NextAuth. To do this, we need to create a middleware.ts file in the root directory and include the following code:

middleware.ts


export { auth as middleware } from './auth';

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};

With the middleware code in place, we can proceed to implement the route protection within the authorized callback function available in the NextAuth config object. Here is an example of how to achieve route protection with NextAuth. In this example, we are only protecting two pages: /profile and /client-side.

auth.ts


export const { handlers, auth, signIn, signOut } = NextAuth({
  session: { strategy: "jwt" },
  adapter: PrismaAdapter(prisma),
  pages: {
    signIn: "/login",
  },
  providers: [
    github,
    google,
    CredentialsProvider({}),
  ],
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user;
      const paths = ["/profile", "/client-side"];
      const isProtected = paths.some((path) =>
        nextUrl.pathname.startsWith(path)
      );

      if (isProtected && !isLoggedIn) {
        const redirectUrl = new URL("/api/auth/signin", nextUrl.origin);
        redirectUrl.searchParams.append("callbackUrl", nextUrl.href);
        return Response.redirect(redirectUrl);
      }
      return true;
    },
    session: ({ session, token }) => {
      return {
        ...session,
        user: {
          ...session.user,
          id: token.id,
          randomKey: token.randomKey,
        },
      };
    },
    jwt: ({ token, user }) => {
      if (user) {
        const u = user as unknown as any;
        return {
          ...token,
          id: u.id,
          randomKey: u.randomKey,
        };
      }
      return token;
    },
  },
});

Conclusion

Congratulations on reaching the end. Throughout this tutorial, you’ve learned how to set up Google and GitHub OAuth providers with NextAuth v5. We started from scratch, creating OAuth credentials on both Google and GitHub, configuring NextAuth to include OAuth sign-in options, modifying the JWT payload and session data, and protecting private routes.

I hope you found this article helpful and enjoyable. If you have any questions or feedback, feel free to leave them in the comment section. Thanks for reading!