Ejiro Asiuwhu Software Engineer with a drive for building highly scalable and performant web applications. Heavily interested in module federation, micro frontends, state machines, TDD, and system designs. Big on web performance and optimization, advanced component design patterns, a11y, SSR, SSG Incremental Static Regeneration (ISR), and state management. Expert at crafting highly reusable Typescript-heavy component libraries. An advocate of TypeScript and industry best practices. I regularly author meaningful technical content ✍🏽.

How to use NextAuth.js for client-side authentication in Next.js

8 min read 2350

How To Use NextAuth.js For Client-Side Authentication In Next.js

Editor’s note: This guide was last updated on 13 June 2023 to include information about creating a custom login page with Next.js, and information about Next.js Middleware, which was released in v12.

Authentication is an important and sensitive feature in applications where a user’s credentials, such as username, email, and password are used to verify their identity.

In this article, we’ll set up client-side authentication that doesn’t require a password in Next.js using a powerful and secure library called NextAuth.js. Our app will allow users to log in using their GitHub, Google, and Facebook accounts. Upon successful signup, we will display the user’s profile picture and email, which we’ll retrieve from their social media accounts.

To build our app, we will use React Hooks and functional components.

Jump ahead:

What is Next.js?

Next.js is a framework built on top of React that makes developing production-ready, fully-optimized React apps fast and easy. It is one of the best things to have come out of the React ecosystem, as it’s very powerful with zero config.

Next.js is used in production by top companies like Netflix, Tiktok, and Nike.

What is NextAuth.js?

NextAuth.js is a completely secured and flexible authentication library designed to sync with any OAuth service, with full support for passwordless signin.

NextAuth.js can be used with or without a database, and it has default support for popular databases such as MySQL, MongoDB, PostgreSQL, and MariaDB. It can also be used without a database by syncing with services like OAuth and JSON Web Token.

How does NextAuth.js work?

With a library like NextAuth.js, you don’t need to be an expert in identity protocol like you would if you were to use OAuth to build secured Next.js applications. NextAuth.js is built to avoid the need to store sensitive data, such as a user’s password. It works perfectly with Next.js. With just 20 lines of code, you can implement an authentication feature in your app.

NextAuth.js has a client-side API you can use to interact with sessions in your app. The session data returned from the Providers contains user payload, and this can be displayed to the user upon successful login.

The session data returned to the client looks like this:

{
  expires: '2055-12-07T09:56:01.450Z';
  user: {
        email: '[email protected]';
        image: 'https://avatars2.githubusercontent.com/u/45228014?v=4';
        name: 'Ejiro Asiuwhu';
    }
}

The payload doesn’t contain any sensitive data. The session payload or data is meant for presentation purposes — that is, it’s meant to be displayed to the user.

NextAuth.js also provides the useSession() React Hook, which can be used to check user login status. Meanwhile, NextAuth.js provides a REST API that is used by the React app. To learn more about what the REST API NextAuth exposes, check out the official docs.

Requirements

  • Node.js installed on your local machine
  • A basic understanding of React.js

Creating the Next.js starter application

To get started, create a new Next.js application by running the command below:

npx create-next-app@latest

You will be prompted to go through a series of questions; your selections should the same as the ones from the screenshot below:

Next.js Starter Application Homepage

Now, change the directory into the project folder and launch the development server:

npm run dev
# or
yarn run dev

By default, the project will run on port 3000. Launch your browser and navigate to http://localhost:3000. You should end up with this:

Launching Our Next.js Project

Setting up authentication with NextAuth.js

Now that we have the Next.js starter application set up, we’re ready to learn how to authenticate a Next.js app with NextAuth.js.



This NextAuth.js client-side authentication tutorial will cover the following:

  • Installing NextAuth.js
  • Creating a GitHub OAuth app
  • Creating a Google OAuth app
  • Creating a Facebook OAuth app

Installing NextAuth.js

next-auth is an npm package, so installing it is easy:

npm install next-auth
# or
yarn add next-auth

In our demo, we’ll give users the choice to log in to our app using their GitHub, Google, or Facebook account.

Creating a GitHub OAuth app

Next, we’re going to add a GitHub Authentication Provider, which essentially allows users to log in to our app using their GitHub account. But first, we need to create a GitHub OAuth app. Click on New OAuth app and fill out the form accordingly. Check out the official docs to learn more:

  • Application name: This is the name of your application. It can be called anything
  • Homepage URL: This is the full URL to the homepage of our app. Because we are still in development mode, we are going to fill in the full URL that our development server is running on. In this case, it is http://localhost:3000
  • Authorization callback URL: This is the URL that GitHub will redirect our users to after they have been successfully logged in to our app. It should be your homepage URL plus /api/auth/callback, resulting in http://localhost:3000/api/auth/callback

After registering our OAuth app, GitHub creates a Client ID and Client Secret specifically for our newly created app. Copy the Client ID and secret key to your clipboard. Click on Generate new client secret to get a Client Secret.

Understanding the Nextauth_secret environment

NEXTAUTH_SECRET is a critical environment variable used by NextAuth.js to sign and verify JWTs (JSON Web Tokens) and encrypt session data. This value should ideally be a long, random string that is kept secure and never exposed publicly. To generate a secret, you can use the Node.js built-in crypto module on your terminal as follows:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

This generates a secure, random, 32-byte value, encoded as a hexadecimal string. Set this string as the value of the NEXTAUTH_SECRET environment variable in your .env.local file with your GitHub client and secret keys:

GITHUB_ID=<GITHUB_CLIENT_ID>
GITHUB_SECRET=<GITHUB_CLIENT_SECRET>
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=<NEXTAUTH_SECRET_KEY>

Replace <GITHUB_CLIENT_ID>, <GITHUB_CLIENT_SECRET>, and <NEXTAUTH_SECRET_KEY> with your GitHub client, secret key, and your NextAuth secret.

Note: If you don’t provide a NEXTAUTH_SECRET variable, NextAuth.js will automatically generate one for you when the server starts. However, this dynamically generated secret won’t persist across server restarts. This means that all your signed tokens and sessions will become invalid every time your server restarts, as they will fail the verification against the new secret. Therefore, it is strongly recommended to provide your own NEXTAUTH_SECRET variable for consistent session and token management in production environments.

Now, back to our app. Create an api/auth/[…nextauth].tsx file in pages and add the following code:

import NextAuth from "next-auth";
import type { NextAuthOptions } from "next-auth";
import GitHubProvider from "next-auth/providers/github";
export const authOptions: NextAuthOptions = {
    providers: [
        GitHubProvider({
            clientId: process.env.GITHUB_ID as string,
            clientSecret: process.env.GITHUB_SECRET as string,
        }),
    ],
    secret: process.env.NEXTAUTH_SECRET,
}
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

In the above code snippet, GitHubProvider is imported from "next-auth/providers/github" and is included in the providers array of the authOptions configuration object, which is of type NextAuthOptions. This object is passed to the NextAuth function, creating a handler for processing authentication requests. The client ID and secret for GitHub are sourced from the environment variables GITHUB_ID and GITHUB_SECRET. Lastly, the handler is exported under the aliases GET and POST, indicating it will manage both types of HTTP requests for authentication routes.

Then, navigate to http://localhost:3000/api/auth/signin and you should see this:

Signing Up With GitHub

Checking user login state with the useSession() Hook

We need to get the login state of our users and render user details on the frontend of our app. To achieve this, NextAuth needs a session provider to be configured on the client-side and wrapped around our component.

Once this is done, we can check user login state and obtain session information using the useSession() Hook. Let’s start by updating the _app.tsx file to a provider.ts file in the app directory and adding the code snippet below:

import { SessionProvider } from 'next-auth/react';
import '../styles/globals.css';
import type { AppProps } from 'next/app';
import React from 'react';
export default function App({
  Component,
  pageProps: { session, ...pageProps },
}: AppProps) {
  return (
    <SessionProvider session={pageProps.session}>
      <Component {...pageProps} />
    </SessionProvider>
  );
}

In the above code snippet, we created a wrapper component (NextAuthProvider) around the SessionProvider component from the next-auth/react package. This will provide session state to all components in your application, allowing you to access session data using the useSession() Hook.

Let’s make use of the useSession() Hook. Replace the code in the index.tsx file with the following code:

import Link from "next/link";
import { useSession, signIn, signOut } from "next-auth/react";
export default function Home() {
  const { data: session } = useSession();
  return (
    <main
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        height: "70vh",
      }}
    >
      <div className="header">
        <Link href="/">
          <p className="logo">NextAuth.js</p>
        </Link>
        {session && (
          <Link href="#" onClick={() => signOut()} className="btn-signin">
            Sign out
          </Link>
        )}
        {!session && (
          <Link href="#" onClick={() => signIn()} className="btn-signin">
            Sign in
          </Link>
        )}
      </div>
    </main>
  );
}

In the above code snippet, we imported the next-auth hooks signIn, signOut, and useSession . The signIn Hook redirects you to the GitHub login page, signOut destroys the activate session and logs the user out, while useSession provides us with the details of the logged in user. So, we used useSession to check if a session exists and conditionally render the signin and and signout links.


More great articles from LogRocket:


Creating a custom login page with NextAuth

So far in your code, you’ve been using the custom login page provided by NextAuth. Now, let’s create a custom login page for your application. To do that, modify the pages/api/auth/[…nextauth].ts file to add a new option to the NextAuth configuration:

...
export const authOptions: NextAuthOptions = {
  providers: [
        ...
    ],
  secret: process.env.NEXTAUTH_SECRET,
  pages: {
      signIn: "/auth/signin",
  },
}
...

Here, you overrode the default pages that NextAuth.js uses for various authentication-related screens. In this case, the signIn property is being set to "/auth/signin", which means that the sign-in page is customized, and NextAuth.js will redirect users to this page when authentication is required.

Now, create a auth/signin.tsx file in the pages directory and add the code snippet below:

import type { GetServerSidePropsContext, InferGetServerSidePropsType } from "next";
import { getProviders, signIn } from "next-auth/react"
import { getServerSession } from "next-auth/next"
import { authOptions } from "../api/auth/[...nextauth]";
export default function SignIn({ providers }: InferGetServerSidePropsType<typeof getServerSideProps>) {
  return (
    <>
      {Object.values(providers).map((provider) => (
        <div key={provider.name}>
          <button onClick={() => signIn(provider.id)}>
            Sign in with {provider.name}
          </button>
        </div>
      ))}
    </>
  )
}
export async function getServerSideProps(context: GetServerSidePropsContext) {
  const session = await getServerSession(context.req, context.res, authOptions);

  // If the user is already logged in, redirect.
  // Note: Make sure not to redirect to the same page
  // To avoid an infinite loop!
  if (session) {
    return { redirect: { destination: "/" } };
  }
  const providers = await getProviders();

  return {
    props: { providers: providers ?? [] },
  }
}

The above code uses server-side rendering to fetch a list of available providers (such as Google, Facebook, etc.) and display them as sign-in buttons. If a user is already logged in, they are redirected to the homepage. The getServerSideProps function fetches the providers and handles session management before the page is rendered. Now if you click on the signin link, you’ll be redirected to the custom signin page.

Understanding NextAuth Middleware

NextAuth Middleware is a key feature of the NextAuth.js library, designed to boost the security of your Next.js apps. Introduced in Next.js 12, it allows you to run certain code before any page (even static ones) is loaded, thus improving both the speed and safety of your website.

The power of Middleware is magnified when used with platforms like Vercel, where it operates at the edge network level, meaning it’s closer to your users, which further enhances performance. One of the main roles of NextAuth Middleware is managing authentication; it verifies users and controls their access to specific parts of your site.

To add Middleware to your Next.js application using NextAuth, create a middleware.ts file in the src folder and define your middleware as follows:

import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/profile')) {
    // Add /profile specific logics
  }
  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    // Add /dashboard specific logics
  }
}
export const config = {
  matcher: ['/profile/:path*', '/dashboard/:path*'],
}

The above code defines a middleware function that interacts with incoming HTTP requests for paths beginning with /profile and /dashboard. Using the NextRequest object from next/server, it accesses the path of the requested URL. For any path that starts with /profile or /dashboard, the corresponding code inside the conditionals executes for these specific paths.

Lastly, the matcher property of the config object dictates which paths, including any subpaths under /profile and /dashboard, the middleware function should be applied to.

Conclusion

In this post, we implemented user authentication in Next.js using NextAuth.js, which is a secured library to identify our users, get user profile information, and render them in our frontend. We covered most of the use cases, but there is a lot more you can do with NextAuth.js, such as adding a database using JSON Web Token, securing pages, and more.

Let me know in the comment section below what you thought of this tutorial. You can also reach me on Twitter and GitHub. Thank you for reading!

LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket. LogRocket Dashboard Free Trial Banner

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — start monitoring for free.

Ejiro Asiuwhu Software Engineer with a drive for building highly scalable and performant web applications. Heavily interested in module federation, micro frontends, state machines, TDD, and system designs. Big on web performance and optimization, advanced component design patterns, a11y, SSR, SSG Incremental Static Regeneration (ISR), and state management. Expert at crafting highly reusable Typescript-heavy component libraries. An advocate of TypeScript and industry best practices. I regularly author meaningful technical content ✍🏽.

13 Replies to “How to use NextAuth.js for client-side authentication in Next.js”

  1. Great article! However, you called it ‘Nuxt-Auth.js’ and ‘Next-Auth.js’ a few times, it is ‘NextAuth.js’

    – core team member

  2. Really great article 😊 I really enjoyed reading it and it makes me want to switch to Next.js. I was wondering why you called this client-side rather than severless? Is the authentication not done server side by SSR? I really apologize if I’m wrong, I’m just trying to understand the authentication flow.

  3. website: next-auth.js
    Example code : next-auth-example
    npm package : next-auth

    But it is called NextAuth.js and not next-auth.js as one could guess from all your urls.

    Am I missing something here ?

  4. Unhandled Runtime Error
    Error: React Context is unavailable in Server Components

    Call Stack
    SessionProvider

    can you please resolve this error

    1. Use the “use client” flag at the top of the file.
      That error is showing up because “SessionProvider” is using react context which is a client component and for it to work, you must mark the file as a client component with the flag above.

      You are using react 13.+.+ I believe

Leave a Reply