In this article, you will learn how to integrate Google and GitHub OAuth providers with NextAuth.js in the new Next.js 13 app directory. It’s worth noting that I am using a specific pull request recommended by the NextAuth team that is compatible with the new Next.js 13 app directory.

To add the pull request or the beta version of the Auth.js library to your Next.js project, you can simply add "next-auth": "0.0.0-pr.6777.c5550344" to your dependencies, or install it via your package manager. While we’re using the NextAuth library in this article, you may be using the Auth.js library by the time you read this. However, the two libraries are interchangeable, and the content of this article should remain valid, as we’re using the beta version of Auth.js.

For the purposes of this tutorial, am going to assume you already have your Google and GitHub client IDs and secrets. However, if you haven’t obtained them yet or are unfamiliar with the process, the last two sections of this article include step-by-step instructions to guide you through it.

More practice:

Next.js - Add Google and GitHub OAuth2 using NextAuth.js

Run the NextAuth Project Locally

To view a demo of the application, you can follow the steps below:

  1. Navigate to the project’s repository located at https://github.com/wpcodevo/nextauth-nextjs13-prisma and clone or download it. Afterwards, open the source code in your preferred code editor.
  2. Within the .env file, add your client IDs and secrets for both GitHub and Google OAuth. If you need assistance obtaining these, follow the instructions outlined in the last two sections at the bottom of this article.
  3. Launch a PostgreSQL server in a Docker container by running the command docker-compose up -d.
  4. Install all the required dependencies with yarn or yarn install.
  5. Apply the Prisma migrations to the Postgres database by executing yarn prisma db push. This command will also generate the Prisma Client within the node modules folder.
  6. Start the Next.js development server with the command yarn dev. Wait for the project to build, then open the URL provided in your preferred browser.
  7. Play around with the app’s functionalities by signing in using Google and GitHub OAuth. You can also create an account with your email and password and use them to access the app.

Explore the GitHub and Google OAuth Flow

Before we dive into the details of how to implement Google and GitHub OAuth with NextAuth, take a look at this demo that shows you how to sign into the app using your GitHub or Google account.

Sign in with Google OAuth2

To sign in with Google OAuth, simply click the “CONTINUE WITH GOOGLE” button on the login page.

The Login Page of the NextAuth.js Project

Assuming you’ve correctly set up your Google OAuth client ID and secret in the .env file, you’ll be redirected to the Google OAuth consent screen where you can select your test Google account.

Google OAuth2 Consent Screen

Once you’ve completed the authentication process, you’ll be redirected to the profile page where you can see your profile picture and other credentials.

Sign in with GitHub OAuth

To sign in with GitHub OAuth, just click on the “CONTINUE WITH GITHUB” button on the login page. You’ll then be taken to the GitHub authorization consent screen, where you’ll need to authorize the Next.js application.

To do this, simply click on the green “authorize” button. Once the authentication is successful, you’ll be redirected to the profile page, where you can view your profile picture and other credentials.

GitHub OAuth Authorization Screen for the NextAuth Project

On the profile page, you can view your GitHub profile picture along with your name and email.

Access the Profile Page after the OAuth Authentication is Successful

Configure NextAuth with Google and GitHub OAuth

Now that you’ve seen the project in action, let’s explore how to implement Google and GitHub OAuth using NextAuth. To do this, we’ll simply add the relevant providers to the NextAuth configuration options and provide the necessary client IDs and secrets.

In some of my previous articles on NextAuth, some users have encountered export errors while trying to run the project when both the NextAuth options and API handlers are defined in the same file. To avoid this issue, we’ll separate the NextAuth configuration into a separate file.

To get started, create a new folder named lib inside the src directory. Inside the lib folder, create an auth.ts file and add the following NextAuth configurations to it.

src/lib/auth.ts


import type { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import GitHubProvider from "next-auth/providers/github";
import GoogleProvider from "next-auth/providers/google";

export const authOptions: NextAuthOptions = {
  pages: {
    signIn: "/login",
  },
  session: {
    strategy: "jwt",
  },
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    }),
    GitHubProvider({
      clientId: process.env.GITHUB_ID as string,
      clientSecret: process.env.GITHUB_SECRET as string,
    }),
    CredentialsProvider({}),
  ],
};

The code above includes references to the OAuth client IDs and secrets for the Google and GitHub providers. To use these providers, you need to provide the corresponding IDs and secrets in your .env file. If you’re not sure how to obtain these values, don’t worry – at the bottom of the article, I’ve included two sections that will guide you through the process of obtaining the Google and GitHub OAuth client IDs and secrets.

With that out of the way, your api/auth/[...nextauth]/route.ts should now look like the one shown below.

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


import { authOptions } from "@/lib/auth";
import NextAuth from "next-auth";

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

If you followed the previous article ‘Next.js – Use Custom Login and SignUp Pages for NextAuth.js‘, and have now added the Google and GitHub OAuth providers to your project, your NextAuthOptions configuration object should resemble the one shown below.

src/lib/auth.ts


import { prisma } from "@/lib/prisma";
import { compare } from "bcryptjs";
import NextAuth from "next-auth";
import type { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import GitHubProvider from "next-auth/providers/github";
import GoogleProvider from "next-auth/providers/google";

export const authOptions: NextAuthOptions = {
  pages: {
    signIn: "/login",
  },
  session: {
    strategy: "jwt",
  },
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    }),
    GitHubProvider({
      clientId: process.env.GITHUB_ID as string,
      clientSecret: process.env.GITHUB_SECRET as string,
    }),
    CredentialsProvider({
      name: "Sign in",
      credentials: {
        email: {
          label: "Email",
          type: "email",
          placeholder: "example@example.com",
        },
        password: { label: "Password", type: "password" },
      },
      async authorize(credentials) {
        if (!credentials?.email || !credentials.password) {
          return null;
        }

        const user = await prisma.user.findUnique({
          where: {
            email: credentials.email,
          },
        });

        if (!user || !(await compare(credentials.password, user.password))) {
          return null;
        }

        return {
          id: user.id,
          email: user.email,
          name: user.name,
          randomKey: "Hey cool",
        };
      },
    }),
  ],
  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;
    },
  },
};

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

Implement the Google and GitHub OAuth2

With the Google and GitHub OAuth providers now added to the NextAuthOptions object, the next step is to implement the logic required to use them in the frontend application. It’s actually pretty straightforward. All you need to do is add the Google and GitHub OAuth buttons to your login form and then when a user clicks on them, you just call the signIn() function and pass in the provider type as the first argument.

Create the Client-Side Form

In Next.js 13.2, components in the app directory are automatically rendered as React Server Components. To use the onClick={} DOM attribute, which is only available in the browser, you need to add the "use client"; directive at the top of the file. This tells Next.js to render the form only in the browser, giving you full access to all the browser APIs.

By default, NextAuth adds a callbackUrl parameter to the URL upon user sign-out, which allows the user to be redirected to their previous page upon their next sign-in. To ensure that the user is redirected to the correct page after signing in, you should check for the presence of this parameter in the URL and pass it to the signIn() method.

To see how all the required parts of authentication are assembled, including the option to use the credential provider or either Google or GitHub OAuth, take a look at the form.tsx component below.

src/app/login/form.tsx


"use client";

import { signIn } from "next-auth/react";
import { useSearchParams, useRouter } from "next/navigation";
import { ChangeEvent, useState } from "react";

export const LoginForm = () => {
  const router = useRouter();
  const [loading, setLoading] = useState(false);
  const [formValues, setFormValues] = useState({
    email: "",
    password: "",
  });
  const [error, setError] = useState("");

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

  const onSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      setLoading(true);
      setFormValues({ email: "", password: "" });

      const res = await signIn("credentials", {
        redirect: false,
        email: formValues.email,
        password: formValues.password,
        callbackUrl,
      });

      setLoading(false);

      console.log(res);
      if (!res?.error) {
        router.push(callbackUrl);
      } else {
        setError("invalid email or password");
      }
    } catch (error: any) {
      setLoading(false);
      setError(error);
    }
  };

  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    const { name, value } = event.target;
    setFormValues({ ...formValues, [name]: value });
  };

  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={onSubmit}>
      {error && (
        <p className="text-center bg-red-300 py-4 mb-6 rounded">{error}</p>
      )}
      <div className="mb-6">
        <input
          required
          type="email"
          name="email"
          value={formValues.email}
          onChange={handleChange}
          placeholder="Email address"
          className={`${input_style}`}
        />
      </div>
      <div className="mb-6">
        <input
          required
          type="password"
          name="password"
          value={formValues.password}
          onChange={handleChange}
          placeholder="Password"
          className={`${input_style}`}
        />
      </div>
      <button
        type="submit"
        style={{ backgroundColor: `${loading ? "#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={loading}
      >
        {loading ? "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"
      >
        <img
          className="pr-2"
          src="/images/google.svg"
          alt=""
          style={{ height: "2rem" }}
        />
        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"
      >
        <img
          className="pr-2"
          src="/images/github.svg"
          alt=""
          style={{ height: "2.2rem" }}
        />
        Continue with GitHub
      </a>
    </form>
  );
};

Create the Server-Side Page Component

Now that you have created the login form component, it’s time to include it in a page component. Despite the page component being a React Server Component, you won’t encounter any issues since we have specified that the form component should only be rendered in the browser. We did this by including the "use client"; directive at the top of the form component.

src/app/login/page.tsx


import { LoginForm } from "./form";
import Header from "@/components/header.component";

export default function LoginPage() {
  return (
    <>
      <Header />
      <section className="bg-ct-blue-600 min-h-screen pt-20">
        <div className="container mx-auto px-6 py-12 h-full flex justify-center items-center">
          <div className="md:w-8/12 lg:w-5/12 bg-white px-8 py-10">
            <LoginForm />
          </div>
        </div>
      </section>
    </>
  );
}

How to Generate the Google OAuth2 Credentials

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. Go to https://console.developers.google.com/ and make sure you’re signed in to 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 on 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. Make sure to use the specific keys that NextAuth expects for the credentials. Here’s an example .env file to guide you.

.env


NEXTAUTH_SECRET=my_ultra_secure_nextauth_secret
NEXTAUTH_URL=http://localhost:3000

GITHUB_ID=
GITHUB_SECRET=

GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

How to Generate the GitHub OAuth Credentials

Follow these steps to register a GitHub OAuth app 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. Congrats, your application has been created! 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. Remember, NextAuth expects specific keys for these credentials. Check out this example .env file to guide you.

.env


NEXTAUTH_SECRET=my_ultra_secure_nextauth_secret
NEXTAUTH_URL=http://localhost:3000

GITHUB_ID=
GITHUB_SECRET=

GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

Conclusion

That’s it! In this tutorial, we went through the process of adding Google and GitHub OAuth authentication to a Next.js application using NextAuth. We also covered how to obtain the required OAuth client IDs and secrets from both Google and GitHub.

I hope you found this tutorial helpful and informative. If you have any feedback or questions, please don’t hesitate to leave them in the comments below. Thank you for reading!