Skip to main content
NextAuth - Google And GitHub Authentications for Nextjs
17 min read

NextAuth - Google And GitHub Authentications for Nextjs

This article was last updated on June 21, 2024, to include new sections on testing, security, error handling, and performance optimization for NextAuth.

Introduction

We know how exhausting and time-consuming it can be to set up authentication, which includes handling databases, cookies, JWT, sessions, etc., on your own. The goal of this article is for you to learn about an alternative and simple tool (NextAuth), which we will use to easily add Google and GitHub authentication to our application.

Steps we’ll cover:

Prerequisites

We will be building a simple landing page to demonstrate how you can simply add Google and GitHub authentication to your Next.js app, and to follow along, you need to have the following in place:

  • Node.js 12.22.0 or later
  • Google console account.
  • GitHub account.

What is NextAuth?

NextAuth.js is an easy-to-implement open source authentication library originally designed for Nextjs and serverless. With NextAuth, you don’t necessarily have to create and use any database or any backend of any kind for authentication because it takes care of all that. It allows your users to sign in with their favorite preexisting logins. You can add as many predefined providers provided by NextAuth to your applications.

Why use NextAuth.js?

  • It supports OAuth 1.0, 1.0A, 2.0, and OpenID Connect and is designed to work with any OAuth service.
  • Many popular sign-in services are already supported.
  • Email and passwordless authentication are supported.
  • Allows for stateless authentication with any backend (Active Directory, LDAP, etc.)
  • JSON Web Tokens and database sessions are both supported.
  • Designed for Serverless but runs anywhere (AWS Lambda, Docker, Heroku, and so on…).
  • An open-source solution that gives you control over your data
  • MySQL, MariaDB, Postgres, SQL Server, MongoDB, and SQLite are all supported natively.
  • Excellent compatibility with popular hosting provider databases
  • It can be used without a database (for example, OAuth + JWT).
  • Secure by default

Project setup

To create a project, run:

yarn create next-app

The command above creates a new Next.js app using create-next-app, which sets up everything automatically for you. You will be prompted to enter your project name to complete the installation. We used the yarn package manager here, feel free to use npm if you have to.

Once the installation is completed, move into your folder directory and run the below command to start the project:

yarn dev

Now your app has started you need to install the NextAuth package which is essential for the authentications.

yarn add next-auth

Once this is done, you will have all the dependencies required to follow up with this tutorial set up and can proceed with s authentication in your next application.

If you are using TypeScript, NextAuth.js comes with its types definitions within the package. To learn more about TypeScript for next-auth, check out the TypeScript documentation

Create API Routes

To get started with adding NextAuth.js to your project, you first create a new folder named auth inside the pages/api directory. Inside this new folder, you will create a file called [...nextauth].js.

This will contains the dynamic route handler for NextAuth.js which will also contain all of your global NextAuth.js configurations.

Next, inside the `[...nextauth].js you've created, you will configure the global NextAuth configurations for Github and Google providers.

pages/api/auth/[...nextauth].js
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import GithubProvider from "next-auth/providers/github";

export default NextAuth({
// Configure one or more authentication providers
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
// ...add more providers here
],
});

Looking at the code snippet above, we started by importing NextAuth from the next-auth package we installed earlier, and since we will be adding only Google and Github auth in this tutorial, GoogleProvider and GithubProvider were also imported and passed in the providers array which stores all NextAuth providers.

Next, we will be looking into how we can obtain the clientId and clientSecret for each of the providers.

For GoogleProvider (Make sure you have a Google account):

Navigate to your Google console credentials tab.

Create a Google project

If you have created and selected a project before now, you will see a button at the top that says "CREATE CREDENTIALS" but if you haven't you need to create a project by clicking on the "CREATE PROJECT" action button like the one circled in the image above. You will be taken to a page where you will enter your project name and organization (optional).

When that is done, ensure you select the project you just created when notified.

Google select project

Once that is done, proceed to create credentials by clicking on the "CREATE CREDENTIALS" action button at the top, this will open a dropdown containing four items. Click on the item circled in the below image to create an OAuth client ID.

Google select auth provider

If you come across anything like:

To create an OAuth client ID, you must first configure your consent screen

Please, go ahead to configure the consent screen by providing some information on your app, and when that is done, proceed to continue creating the OAuth client ID by choosing the application type and filling up the rest of the fields.

Create an auth client

After entering the name of your OAuth 2.0 client. You will see a section that requests adding an "Authorized JavaScript origins", click on the "+ ADD URI" action button to add your app origin. Since we are working in the development environment, all we need to do is to copy/paste our app URL which is running on localhost.

Authorized origins

For the "Authorized redirect URIs", you must include your full domain and end in the callback path.

  • For production: https://{YOUR_DOMAIN}/api/auth/callback/google
  • For development: http://localhost:3000/api/auth/callback/google
Redirect URL
NOTE

If you eventually push to production, you need to change the Authorized JavaScript Origin and Authorized redirect URIs to the production URIs.


Please verify the details provided and if they are all correct, click on "CREATE" to finally create your OAuth client ID.

Auth client verified

Congratulations! You've successfully created Google OAuth client ID and SECRET.

Where do you store it?

Next, you will create a .env.local file in the root level of your app folder and inside of it you will create GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET environment variables and assign the OAuth client values we just created for each.

You also need to include NEXTAUTH_URL (assign your app origin) and NEXTAUTH_SECRET environment variables in the .env.

When deploying to production, set the NEXTAUTH_URL environment variable to the canonical URL of your site.

Create a NEXTAUTH_SECRET environment variable for all environments.

Your .env.local file should be looking like this when you're done.

.env
GOOGLE_CLIENT_ID=YOUR_CLIENT_ID
GOOGLE_CLIENT_SECRET=YOUR_CLIENT_SECRET

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

For GithubProvider (you will need a GitHub account):

To create your GithubProvider ID and SECRET, navigate to your GitHub account developer settings and click on the OAuth Apps tab to register a new application.

Github register

Complete application registration by filling out the fields.

Github register enable

Once completed, click on the "Register application" action button to register your app. Remember to go back and change the URIs to your production URI once you push to production.

Github secrets

Congratulations! You've just created your Github OAuth Client ID and SECRET. You can generate a new client secret by clicking on the "Generate a new client secret" action button.

Recall you've already created a .env file for your environment variables, go ahead and assign the Client ID and Client secrets you just generated to the GITHUB_ID and GITHUB_SECRET variables.

.env
GOOGLE_CLIENT_ID=YOUR_CLIENT_ID
GOOGLE_CLIENT_SECRET=YOUR_CLIENT_SECRET

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

GITHUB_ID=YOUR_ID
GITHUB_SECRET=YOUR_SECRET

This is the final look of our .env file.

Next, you will head into your [...nextauth].js file and add the secret, remember you've already added the environment variable as NEXTAUTH_SECRET.

pages/api/auth/[...nextauth].js
import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import GithubProvider from "next-auth/providers/github";

export default NextAuth({
// Configure one or more authentication providers
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
// ...add more providers here
],
secret: process.env.NEXTAUTH_SECRET,
});

Congratulations! You’ve added your complete API route. The next thing you are going to do is to configure the shared session state.

Configure Shared Session State

The useSession() React Hook in the NextAuth.js client is the easiest way to check if someone is signed in, and to be able to use this hook, first you’ll need to expose the session context, <SessionProvider />, at the top level of your application:

pages/_app.jsx
import { SessionProvider } from "next-auth/react";

export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
}

Instances of useSession will then have access to the session data and status. The <SessionProvider /> also takes care of keeping the session updated and synced between browser tabs and windows.

Update Page Components

Now update the pages/index.js file to create a blog application. To that, add the code snippet below to the page/index.js.

page/index.js
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";

export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>My Blog</h1>
<div className={styles.row}>
<div className={styles.blogCard}>
<Image
src="/Getting-Started-with-NextJS-Inside.jpeg"
alt="blog1"
width={300}
height={200}
/>
<div className={styles.blogCardContent}>
<h2>Nexjs for Beginers</h2>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Quisquam, quidem.
</p>

<a href="/blog1">Read More</a>
</div>
</div>
<div className={styles.blogCard}>
<Image
src="/pasted image 0.png"
alt="blog1"
width={300}
height={200}
/>
<div className={styles.blogCardContent}>
<h2>Django Full Course</h2>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Quisquam, quidem.
</p>

<a href="/blog1">Read More</a>
</div>
</div>
</div>
</main>
</div>
);
}

Then add the code snippets in the styles/Home.module.css file to style the application.

styles/Home.module.css
.blogCard {
margin: 1rem;
padding: 1.5rem;
text-align: left;
color: inherit;
text-decoration: none;
border: 1px solid #eaeaea;
border-radius: 10px;
transition: color 0.15s ease, border-color 0.15s ease;
max-width: 300px;
}
.blogCardContent {
margin-top: 1rem;
}

.login {
margin: 1rem;
padding: 1.5rem;
text-align: left;
color: inherit;
text-decoration: none;
border: 1px solid #eaeaea;
border-radius: 10px;
transition: color 0.15s ease, border-color 0.15s ease;
max-width: 300px;
}
.row {
display: flex;
flex-wrap: wrap;
width: 100%;
margin-right: -15px;
margin-left: -15px;
justify-content: center;
}
.header {
display: flex;
align-items: center;
}
.header button {
margin-left: 10rem;
padding: 4px;
height: 2rem;
background-color: #ad270c;
border: none;
}

So if you refresh the application, you should see the blog application as shown in the screenshot below.

Result view

Right now, everyone can have access to the application. Let's add a hook to restrict access to only authenticated users.

Add React Hook

To know if a user is authenticated, we'll use the useSession() hook. To get started, we need to import and update the pages/index.js with the code snippet below.

pages/index
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";
import { useSession, signIn, signOut } from "next-auth/react";

export default function Home() {
const { data: session } = useSession();
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
{!session ? (
<>
<p>Not signed in</p>
<br />
<button onClick={() => signIn()}>Sign in</button>
</>
) : (
<main className={styles.main}>
<div className={styles.header}>
<h4>Signed in as {session.user.name}</h4>
<button onClick={() => signOut()}>Sign out</button>
</div>
<h1 className={styles.title}>My Blog</h1>
<div className={styles.row}>
<div className={styles.blogCard}>
<Image
src="/Getting-Started-with-NextJS-Inside.jpeg"
alt="blog1"
width={300}
height={200}
/>
<div className={styles.blogCardContent}>
<h2>Nexjs for Beginers</h2>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Quisquam, quidem.
</p>

<a href="/blog1">Read More</a>
</div>
</div>
<div className={styles.blogCard}>
<Image
src="/pasted image 0.png"
alt="blog1"
width={300}
height={200}
/>
<div className={styles.blogCardContent}>
<h2>Django Full Course</h2>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Quisquam, quidem.
</p>

<a href="/blog1">Read More</a>
</div>
</div>
</div>
</main>
)}
</div>
);
}

Let's point out what we did in the above code. First, we NextAuth hooks, the useSession which helps us know if a user is authenticated, the signIn hook to load the sign-in buttons and the signOut hook to log a user out. Then we destructured the session object from the useSession hook. The session object provides us with the information of the logged-in user. Using conditional rendering, we check if a user is logged in and render the blogs else show the user a sign-in button.

Now, if you refresh the application, it will look like the screenshot below for non-authenticated users.

Not sign in

When the user clicks the sign-in button, they will be redirected to the sign-in button page as shown in the screenshot below.

Sign in box

Now when they click on any of the buttons, they will be redirected to the Google or Github page depending on which button they click. Once they are authenticated, the application will look like the screenshot below.

Sign in result

Protect API Routes

NextAuth also allows you to protect your API routes. You can use the unstable_getServerSession() method to do that. Let's update and protect the pages/api/hello.js API with the code snippet below.

pages/api/hello.js
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { unstable_getServerSession } from "next-auth/next";
import { authOptions } from "../api/auth/[...nextauth]";

export default async function handler(req, res) {
const session = await unstable_getServerSession(req, res, authOptions);
if (session) {
const blogData = [
{
id: 1,
title: "Blog 1",
content:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quidem.",
image: "/images/blog1.jpg",
},
{
id: 2,
title: "Blog 2",
content:
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quidem.",
image: "/images/blog2.jpg",
},
];
res.status(200).json(blogData);
} else {
res.status(401).json({ message: "Unauthorized" });
}
}

In the above code snippet, we imported the unstable_getServerSession method and the authOptions props. Then we created a session object from the unstable_getServerSession method passing in the req, res and authOptions as arguments. The session object will provide us with the details of the logged-in user. We check if a session exists and send the blog data to the client else send a 401 (Unauthorized) response.

Testing Authentication

Testing on authentication in my project using NextAuth.js. Auth flow is significant, so here is the process.

The first step involves writing unit tests for my authentication logic. I use Jest, which is a great testing framework for JavaScript. For example, I am going to test out my API routes to ensure that they are handling login and logout properly.

The following is a simple test that verifies the success response of the login route:

import { createMocks } from "node-mocks-http";
import handleLogin from "./pages/api/auth/[...nextauth]";

test("login API returns success", async () => {
const { req, res } = createMocks({
method: "POST",
body: {
provider: "google",
token: "sample-token",
},
});

await handleLogin(req, res);

expect(res.statusCode).toBe(200);
expect(res._getJSONData().message).toBe("Login successful");
});

I also make sure the whole authentication flow works using end-to-end integration tests. I write it using the likes of Cypress and for instance, simulating user interactions like signing in and out and verifying whether an application behaves in a certain way. The following is an example of an integration test with Cypress:

describe("Authentication Flow", () => {
it("should allow a user to sign in and sign out", () => {
cy.visit("/");
cy.contains("Sign in").click();
cy.url().should("include", "/auth/signin");
cy.get('input[name="email"]').type("user@example.com");
cy.get('input[name="password"]').type("password");
cy.contains("Sign in").click();
cy.url().should("include", "/");
cy.contains("Sign out").click();
cy.contains("Sign in");
});
});

Lastly, I mock the OAuth providers during tests to avoid hitting real provider APIs. This makes tests much faster and saves you from dealing with issues around rate limiting or network problems during the test. I use libraries such as nock for HTTP request mocking.

Security Considerations

When building your app and implementing authentication, it is very important to ensure the security of your users' data. One major step to take in this regard is using environment variables to store sensitive information such as client IDs and secrets so that it is out of the code base.

Always make sure to use HTTPS for transmission of data between the client and server, which helps to avoid attackers from intercepting sensitive information. Also, the session information may be maintained securely with cookies. The cookie attribute may be applied along with HttpOnly to avoid an XSS attack.

Correct handling of the sessions is also essential. The session should be given a proper expiry time and invalidated when the user logs out. Another best practice is the updating of dependencies to safeguard against known vulnerabilities within libraries, including but not limited to Next.js, NextAuth.js, etc. Last but not least, apply the rate limit on your authentication endpoints to protect against brute-force attacks.

By these best practices, you can further secure the implementation of your authentication and provide better protection for data belonging to your users.

Error Handling

I use tools like Sentry and LogRocket to track and diagnose these issues. They help me spot patterns and recurring problems. For custom error handling, I set up custom error pages using the pages/_error.js file in Next.js. This way, users get guided through troubleshooting steps if something goes wrong.

Additionally, I use middleware to catch and manage errors at different stages of the authentication process. The signIn and signOut methods from NextAuth.js can include error handling logic to manage specific authentication failures.

By using these tools and methods, I can handle authentication errors smoothly, which improves the user experience and keeps the application secure.

Some Performance Optimization Tips

To begin with, one of the excellent ways to boost authentication performance is by using caching. You can implement a caching strategy about OAuth tokens, cutting down on the number of requests made to the authentication provider.

Another efficient way is to reduce the size of data sent and received. I made these requests on Google, GitHub, and the like, requesting only the scopes needed and the data required out of them, which has helped in making my payloads small and efficient.

Plus, all my dependencies are up-to-date, so I have everything at the best performance and without bugs in libraries like Next.js and NextAuth.js.

Finally, I use a Content Delivery Network (CDN) to serve static assets quickly. This dramatically reduces the load on my server and speeds up general application performance. With those strategies in place, my authentication process will be fast and effective.

Conclusion

Throughout this tutorial, we've implemented how to add Google and GitHub authentication to a Nextjs application using NextAuth. We started by knowing what NextAuth is and why you should use it. Then we built a blog application for the demonstration. Now that you have the knowledge you seek, how would you add authentication to your next Nextjs project? Perhaps you can learn more about NextAuth from the documentation.


discord banner