In this article, you’ll learn how to set up NextAuth v5 within a Next.js 15 project. On October 21, 2024, the Next.js team announced that Next.js 15 is officially stable and ready for production.

Although this update may not be groundbreaking, it brings significant enhancements to key APIs.

I’ve received numerous requests to cover the integration of NextAuth v5 with this latest version, so I’m excited to guide you through the process. The setup is quite simple; all you need to do is install the beta version of NextAuth that’s compatible with Next.js 15. Let’s get started!

More practice

How to Set Up Next.js 15 with NextAuth v5

Run and Test the NextAuth v5 Integration Locally

To run the NextAuth v5 integration on your local machine, follow the steps below:

  1. Download or clone the project from its GitHub repository at https://github.com/wpcodevo/nextauth-nextjs15-prisma, and then 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 by running the command docker-compose up -d .

    Alternatively, create a serverless PostgreSQL database on https://neon.tech/ and add the connection string to the DATABASE_URL variable in the .env file.
  5. Synchronize the Prisma migrations with the PostgreSQL database schema by running the command pnpm prisma migrate dev.
  6. Open the .env file and add your Google and GitHub OAuth credentials in the designated fields. If you need help obtaining these credentials, follow the instructions at codevoweb.com.
  7. Start the Next.js development server by running the command pnpm dev. Once the server is ready, open the application’s homepage in your browser. You should be automatically redirected to the default NextAuth sign-in page.
  8. On the default NextAuth sign-in page, log in using the Google or GitHub OAuth option. Once authenticated, you’ll be redirected to the homepage, where your public profile information will be displayed.

Install NextAuth Dependencies

Now that you’ve experienced the final project, let’s move on to initializing NextAuth in your Next.js 15 project. To do this, start by installing the NextAuth beta package along with the Prisma adapter using the commands below. Note that to make NextAuth compatible with Next.js 15, you’ll need to install the beta version.


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

Configure Prisma for Persistent Data Storage

Before initializing Prisma, we need to set up a database. In this tutorial, we’ll use PostgreSQL and rely on Docker to easily spin up a PostgreSQL server. If you already have a PostgreSQL database running in the cloud, you can skip this step—just be sure to add its connection URL to the .env file.

Now, create a docker-compose.yml file in the root directory and add the following Docker Compose configuration:


services:
  postgres:
    image: postgres:latest
    container_name: postgres
    ports:
      - '6500:5432'
    volumes:
      - progresDB:/var/lib/postgresql/data
    env_file:
      - ./.env
volumes:
  progresDB:

The Docker configuration references a .env file to supply Docker Compose with the credentials required to launch the PostgreSQL server. To set this up, create a .env file in the root directory and add the following environment variables:


AUTH_GITHUB_ID=
AUTH_GITHUB_SECRET=

AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=

AUTH_SECRET=my_ultra_secure_nextauth_secret
# NEXTAUTH_URL=http://localhost:3000 this is causing a webpack error in the beta v8 of NextAuth

POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=6500
POSTGRES_USER=admin
POSTGRES_PASSWORD=password123
POSTGRES_DB=nextauth_prisma

DATABASE_URL="postgresql://admin:password123@localhost:6500/nextauth_prisma?schema=public"

Once you have the credentials in place, run the command docker-compose up -d to launch the PostgreSQL server.

Now that we have a running PostgreSQL server, install the Prisma-related dependencies using the commands below:


pnpm add @prisma/client bcryptjs
pnpm add -D prisma ts-node @types/bcryptjs

# or
yarn add @prisma/client bcryptjs
yarn add -D prisma ts-node @types/bcryptjs

# or
npm i @prisma/client bcryptjs
npm i -D prisma ts-node @types/bcryptjs

After the dependencies have been installed, use the command below to initialize Prisma in the project:


pnpm prisma init --datasource-provider postgresql

# or
yarn prisma init --datasource-provider postgresql

# or
npm run prisma init --datasource-provider postgresql

Next, we need to define the Prisma models required by NextAuth to handle authentication for our application. Open the prisma/schema.prisma file and add the following Prisma code:

prisma/schema.prisma


// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id            String    @id @default(uuid())
  name          String
  email         String?   @unique
  password      String?
  emailVerified DateTime? @map("email_verified")
  image         String?
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
  accounts      Account[]
  sessions      Session[]

  @@map("users")
}

model Account {
  id                String   @id @default(cuid())
  userId            String   @map("user_id")
  type              String?
  provider          String
  providerAccountId String   @map("provider_account_id")
  token_type        String?
  refresh_token     String?  @db.Text
  access_token      String?  @db.Text
  expires_at        Int?
  scope             String?
  id_token          String?  @db.Text
  createdAt         DateTime @default(now())
  updatedAt         DateTime @updatedAt
  user              User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
  @@map("accounts")
}

model Session {
  id           String   @id @default(cuid())
  userId       String?  @map("user_id")
  sessionToken String   @unique @map("session_token") @db.Text
  accessToken  String?  @map("access_token") @db.Text
  expires      DateTime
  user         User?    @relation(fields: [userId], references: [id], onDelete: Cascade)
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt

  @@map("sessions")
}

model VerificationRequest {
  id         String   @id @default(cuid())
  identifier String
  token      String   @unique
  expires    DateTime
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt

  @@unique([identifier, token])
}

With the Prisma models defined, run the command pnpm prisma migrate dev --name init to generate the migration files and synchronize the PostgreSQL database with the migrations.

One final step we need to address is to create a singleton function that will enable our application to connect to the Postgres database via Prisma. To do this, create a prisma.ts file in the ‘prisma‘ directory.

prisma/prisma.ts


import { PrismaClient } from '@prisma/client';

const prismaClientSingleton = () => {
  return new PrismaClient();
};

declare global {
  var prisma: undefined | ReturnType<typeof prismaClientSingleton>;
}

const prisma = globalThis.prisma ?? prismaClientSingleton();

export default prisma;

if (process.env.NODE_ENV !== 'production') globalThis.prisma = prisma;

Define NextAuth Configuration Settings

Next, at the root of your project, create an auth.ts file and insert the following code. This code configures NextAuth v5 and exports a handler function, which will be used to set up the dynamic HTTP route required by NextAuth in Next.js.

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";
import bcrypt from "bcryptjs";

export const { handlers, auth, signIn, signOut } = NextAuth({
  session: { strategy: "jwt" },
  adapter: PrismaAdapter(prisma),
  providers: [
    github,
    google,
    CredentialsProvider({
      name: "Sign in",
      id: "credentials",
      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: String(credentials.email),
          },
        });

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

        return {
          id: user.id,
          email: user.email,
          name: user.name,
          randomKey: "Hey cool",
        };
      },
    }),
  ],
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user;
      // const paths = ["/profile", "/client-side"];
      const paths = ["/"];
      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;
    },
    jwt: ({ token, user }) => {
      if (user) {
        const u = user as unknown as any;
        return {
          ...token,
          id: u.id,
          randomKey: u.randomKey,
        };
      }
      return token;
    },
    session(params) {
      return {
        ...params.session,
        user: {
          ...params.session.user,
          id: params.token.id as string,
          randomKey: params.token.randomKey,
        },
      };
    },
  },
});

Create the NextAuth API Route

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

  1. Go into 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:

Ensure you manually type the dynamic folder [...nextauth] to avoid encountering errors, as the three dots will be changed to ellipses if you copy them from this article.

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


import { handlers } from '@/auth';

export const { GET, POST } = handlers;

Next, add the AUTH_SECRET variable to your .env file and use the command openssl rand -hex 32 to generate a unique string value for it. This secret will be used by NextAuth to sign and decode the JSON Web Token.

.env


AUTH_SECRET=my_ultra_secure_nextauth_secret

Retrieve NextAuth Sessions in API Routes

We have now successfully integrated NextAuth v5 into our Next.js 15 project. Next, let’s test the authentication flow by creating a route that returns the session object of the authenticated user. In the api directory, create a new folder called session. Inside the session directory, create a route.ts file and add the following code:

src/app/api/session/route.ts


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

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,
  });
}

When you access the /api/session route in your browser after logging into the application, you should see a JSON object containing your public OAuth credentials. If you attempt to access the route while not logged in, you will be redirected to the default NextAuth login page.

the nextauth session object from the api route

Access NextAuth Sessions in Page Components

Next, let’s explore how to access the session object within a page component. To do this, open the src/app/page.tsx file and replace its existing content with the code provided below:

src/app/page.tsx


import Image from "next/image";
import { redirect } from "next/navigation";
import { auth } from "../../auth";

export default async function ProfilePage() {
  const session = await auth();

  if (!session?.user) {
    return redirect("/api/auth/signin");
  }

  const user = session?.user;

  return (
    <section className="to-blue-600  min-h-screen pt-20">
      <div className="max-w-4xl mx-auto bg-ct-dark-100 rounded-md h-[20rem] flex justify-center items-center">
        <div>
          <p className="mb-3 text-5xl text-center font-semibold">
            Profile Page
          </p>
          <div className="flex items-center gap-8">
            <div>
              <Image
                src={user?.image ? user.image : "/default.png"}
                alt={`profile photo of ${user?.name}`}
                width={90}
                height={90}
              />
            </div>
            <div className="mt-8">
              <p className="mb-3">ID: {user?.id}</p>
              <p className="mb-3">Name: {user?.name}</p>
              <p className="mb-3">Email: {user?.email}</p>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

When you access the home page, you should see your user information displayed as shown in the screenshot below. As always, please remember that you need to be signed in to access this page.

nextauth credentials displayed on the screen after login

Conclusion

We’ve now concluded this article. In this comprehensive guide, you have learned how to set up NextAuth v5 in your Next.js 5 project. I hope you found this article both informative and enjoyable. If you have any questions or feedback, please feel free to share your thoughts in the comment section below.