Build With Abdallah logo Build With Abdallah Software · AI · Automation
Tutorial 6 min read Jun 06, 2026

Implementing Advanced Authentication in Next.js 16 with NextAuth.js

In the modern web development landscape, securing user authentication is crucial. Next.js 16, combined with NextAuth.js, offers a powerful solution for implementing authentication

A
Abdallah Mohamed
Senior Full-Stack Engineer
Implementing Advanced Authentication in Next.js 16 with NextAuth.js

Implementing Advanced Authentication in Next.js 16 with NextAuth.js

In the modern web development landscape, securing user authentication is crucial. Next.js 16, combined with NextAuth.js, offers a powerful solution for implementing authentication in your applications. NextAuth.js is a complete open-source authentication solution for Next.js applications, providing a flexible and secure way to manage user sessions.

This tutorial will guide you through setting up advanced authentication in a Next.js 16 project using NextAuth.js. We will cover the necessary steps to get a basic authentication system up and running, and explore features like social login and secure session management.

Prerequisites

Before we begin, ensure you have the following installed on your machine:

  • Node.js (version 16 or later)
  • npm (Node package manager)

You can verify your Node.js and npm versions by running:

node -v
npm -v

If you need to install Node.js and npm, you can download them from the official Node.js website.

Additionally, ensure you have a basic understanding of JavaScript and familiarity with Next.js.

Project Structure

Let's start by setting up the project structure. We'll create a new Next.js application and install NextAuth.js.

npx create-next-app@latest nextauth-demo
cd nextauth-demo
npm install next-auth

Your project directory should look like this:

nextauth-demo/
├── node_modules/
├── public/
├── styles/
├── pages/
│   ├── api/
│   │   └── auth/
│   ├── _app.js
│   └── index.js
├── .gitignore
├── package.json
└── README.md

Step 1: Set Up NextAuth.js

First, we'll configure NextAuth.js to handle authentication. Create a new file [...]nextauth.js inside the pages/api/auth directory:

touch pages/api/auth/[...nextauth].js

Add the following code to configure NextAuth.js:

// pages/api/auth/[...nextauth].js
import NextAuth from "next-auth";
import Providers from "next-auth/providers";

export default NextAuth({
  providers: [
    Providers.Google({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET,
});

Explanation:

  • We're using Google as an authentication provider. You'll need to set up a Google OAuth client to get the clientId and clientSecret.
  • The secret is used to encrypt the session, which you should set in your environment variables.

Step 2: Configure Environment Variables

NextAuth.js requires certain environment variables to be set for security and provider configuration. Create a .env.local file in the root of your project:

touch .env.local

Add the following environment variables:

GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
NEXTAUTH_SECRET=your-nextauth-secret

Explanation:

  • Replace your-google-client-id and your-google-client-secret with the credentials from your Google OAuth setup.
  • NEXTAUTH_SECRET should be a random string used to encrypt session data.

Step 3: Create a Login Page

Now, let's create a simple login page where users can authenticate via Google. Create a new file login.js inside the pages directory:

touch pages/login.js

Add the following code to the login.js file:

// pages/login.js
import { signIn, signOut, useSession } from "next-auth/react";

export default function LoginPage() {
  const { data: session } = useSession();

  return (
    <div>
      {!session ? (
        <>
          <h1>Login</h1>
          <button onClick={() => signIn("google")}>Sign in with Google</button>
        </>
      ) : (
        <>
          <h1>Welcome, {session.user.name}</h1>
          <button onClick={() => signOut()}>Sign out</button>
        </>
      )}
    </div>
  );
}

Explanation:

  • We use the useSession hook from NextAuth.js to get the session data.
  • If a user is not logged in, we display a button to sign in with Google.
  • If a user is logged in, we greet them by name and provide a sign-out button.

With these steps, you have a basic authentication setup using NextAuth.js in your Next.js application. In the next part of this tutorial, we will cover more advanced features and configurations.


```markdown
## Step 4: Protecting Routes

To ensure that only authenticated users can access certain pages, we need to protect our routes. Let's create a higher-order component (HOC) that will wrap around pages requiring authentication.

Create a new file `requireAuth.js` inside a `components` directory:

```bash
mkdir components
touch components/requireAuth.js

Add the following code to requireAuth.js:

// components/requireAuth.js
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";

export default function requireAuth(Component) {
  return function AuthenticatedComponent(props) {
    const { data: session, status } = useSession();
    const router = useRouter();

    useEffect(() => {
      if (status === "loading") return; // Do nothing while loading
      if (!session) router.push("/login"); // Redirect to login if not authenticated
    }, [session, status]);

    if (status === "loading" || !session) {
      return <p>Loading...</p>; // Show loading while checking session
    }

    return <Component {...props} />;
  };
}

Explanation:

  • This HOC checks if a user is authenticated using the useSession hook.
  • If the session is loading, it displays a loading message.
  • If the user is not authenticated, it redirects them to the login page.
  • If authenticated, it renders the wrapped component.

Step 5: Apply Route Protection

Let's apply our requireAuth HOC to a protected page. Create a new file dashboard.js inside the pages directory:

touch pages/dashboard.js

Add the following code to dashboard.js:

// pages/dashboard.js
import requireAuth from "../components/requireAuth";

function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <p>This is a protected route. Only authenticated users can see this.</p>
    </div>
  );
}

export default requireAuth(Dashboard);

Explanation:

  • We wrap the Dashboard component with requireAuth to ensure only authenticated users can access it.

Complete Working Example

Here's a summary of the full files for a working authentication system:

pages/api/auth/[...nextauth].js

import NextAuth from "next-auth";
import Providers from "next-auth/providers";

export default NextAuth({
  providers: [
    Providers.Google({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET,
});

.env.local

GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
NEXTAUTH_SECRET=your-nextauth-secret

pages/login.js

import { signIn, signOut, useSession } from "next-auth/react";

export default function LoginPage() {
  const { data: session } = useSession();

  return (
    <div>
      {!session ? (
        <>
          <h1>Login</h1>
          <button onClick={() => signIn("google")}>Sign in with Google</button>
        </>
      ) : (
        <>
          <h1>Welcome, {session.user.name}</h1>
          <button onClick={() => signOut()}>Sign out</button>
        </>
      )}
    </div>
  );
}

components/requireAuth.js

import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";

export default function requireAuth(Component) {
  return function AuthenticatedComponent(props) {
    const { data: session, status } = useSession();
    const router = useRouter();

    useEffect(() => {
      if (status === "loading") return;
      if (!session) router.push("/login");
    }, [session, status]);

    if (status === "loading" || !session) {
      return <p>Loading...</p>;
    }

    return <Component {...props} />;
  };
}

pages/dashboard.js

import requireAuth from "../components/requireAuth";

function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <p>This is a protected route. Only authenticated users can see this.</p>
    </div>
  );
}

export default requireAuth(Dashboard);

Common Errors and Fixes

Error: OAuthCallbackError: Missing client_id

  • Cause: The GOOGLE_CLIENT_ID environment variable is not set correctly.
  • Fix: Ensure your .env.local file contains the correct GOOGLE_CLIENT_ID.

Error: Cannot find module 'next-auth/providers'

  • Cause: NextAuth.js is not installed.
  • Fix: Run npm install next-auth to install the package.

Error: Invalid secret

  • Cause: The NEXTAUTH_SECRET is missing or incorrect.
  • Fix: Set a valid NEXTAUTH_SECRET in your .env.local file.

Conclusion

You've now set up a basic authentication system using NextAuth.js in a Next.js 16 application. This tutorial covered setting up Google as an authentication provider, protecting routes, and handling sessions. You can expand this setup by adding more providers and customizing session handling.

Sources