Next.js 14 Authentication using Prisma and Auth.js: A Complete Guide

6 min read
·

Authentication is an essential part of any web application. It allows users to log in, create an account, and access their personalized content.

Next.js 14 App Router and Auth.js are still new and not many tutorials exist on how to implement them. The Next.js App Router is a new router built on top of the Next.js Router. It provides a simpler and more intuitive way to handle routing in Next.js applications. Auth.js is a library that provides authentication and authorization functionality for Next.js applications. Auth.js is still in beta, meaning it’s not production-ready yet. So, be careful when using it in production.

In this tutorial, we will learn how to implement authentication using Next.js and Auth.js. We will use Google as our authentication provider and Prisma as our database ORM.

Prerequisites

Before we start, make sure you have the following:

  • Node.js 18.18 or higher installed on your machine
  • PostgreSQL
  • A code editor or IDE

Setting up the project

To get started, create a new Next.js project using the following command:

Terminal window
npx create-next-app@latest

This will create a new Next.js project with the necessary dependencies and configuration files.

Installing Prisma & Auth.js

To install Prisma ORM and Auth.js, run the following command:

Terminal window
npm install next-auth@beta
npm install @prisma/client @auth/prisma-adapter
npm install prisma --save-dev
# Generate auth secret
npx auth secret
# Set up Prisma
npx prisma init

It will create a new file named schema/schema.prisma in the root of your project.

Configuring Prisma

Remove .env.local since Prisma doesn’t support .env.local syntax.

Open .gitignore file and add:

.gitignore
.env

Open .env file and modify the following code:

.env
DATABASE_URL=postgres://postgres:[email protected]:5432/db

Modify the DATABASE_URL with your database url. Don’t forget to replace /db with your database or schema name.

Then, add the following code to the schema/schema.prisma file:

schema/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Account {
id String @id @default(cuid())
userId String @map("user_id")
type String
provider String
providerAccountId String @map("provider_account_id")
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
@@map("accounts")
}
model Session {
id String @id @default(cuid())
sessionToken String @unique @map("session_token")
userId String @map("user_id")
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("sessions")
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime? @map("email_verified")
image String?
accounts Account[]
sessions Session[]
// Optional for WebAuthn support
Authenticator Authenticator[]
@@map("users")
}
model VerificationToken {
identifier String
token String
expires DateTime
@@unique([identifier, token])
@@map("verificationtokens")
}
// Optional for WebAuthn support
model Authenticator {
credentialID String @unique @map("credential_id")
userId String @map("user_id")
providerAccountId String @map("provider_account_id")
credentialPublicKey String @map("credential_public_key")
counter Int
credentialDeviceType String @map("credential_device_type")
credentialBackedUp Boolean @map("credential_backed_up")
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([userId, credentialID])
@@map("authenticators")
}

Execute the migration using:

Terminal window
# migrate the database
npm exec prisma migrate dev
# generate the prisma client
npm exec prisma generate

Configuring Auth.js

Create file named auth.ts in the root of your project and add the following code:

auth.ts
import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
import Google from "next-auth/providers/google"
const prisma = new PrismaClient()
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [Google],
})

Create and modify file:

app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth" // Referring to the auth.ts we just created
export const { GET, POST } = handlers

Registering a Google OAuth client

Before we can use Google as our authentication provider, we need to register a Google OAuth client. To do this, follow these steps:

  1. Go to the Google Cloud Console and create a new project.
  2. Go to the APIs & Services section and enable the Google Identity and Access Management (IAM) API.
  3. Go to the Credentials section and create a new OAuth client ID.
  4. Fill Authorized JavaScript origins with http://localhost:3000
  5. Fill Authorized redirect URIs with
http://localhost:3000/api/auth/callback/google
  1. Click Save
  2. Copy the client ID and client secret and add them to your .env file.
.env
AUTH_GOOGLE_ID=YOUR_CLIENT_ID
AUTH_GOOGLE_SECRET=YOUR_CLIENT_SECRET

Implementing authentication

Now that we have the signIn function set up, we can implement authentication in our application. To do this, we’ll use the signIn function to sign in the user.

Let’s create a simple login page.

app/login/page.tsx
import { signIn } from "@/auth"
export default function SignIn() {
return (
<form
className="flex flex-col items-center justify-center gap-4 p-8 text-center"
action={async () => {
"use server"
await signIn("google", {
redirectTo: "/protected",
})
}}
>
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Signin with Google
</button>
</form>
)
}

Create a protected page.

app/protected/page.tsx
import { auth, signOut } from "@/auth"
export default async function ProtectedPage() {
const session = await auth()
if (!session) {
return (
<div className="flex flex-col items-center justify-center gap-4 p-8 text-center">
<h1>Protected Page</h1>
<p>You are not signed in</p>
<a href="/login">
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Signin
</button>
</a>
</div>
)
}
return (
<div className="flex flex-col items-center justify-center gap-4 p-8 text-center">
<h1>Protected Page</h1>
<p>You are signed in as {session.user!.email}</p>
<form
action={async () => {
"use server"
await signOut()
}}
>
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Signout
</button>
</form>
</div>
)
}

Testing

So far, we have implemented the signIn function to authenticate users. Now, let’s test the authentication.

Case 1: Signing in with a user that is not signed in

  1. Open your browser and navigate to http://localhost:3000/login.
  2. Click the “Signin with Google” button.
  3. You should be redirected to Google’s login page.
  4. Enter your Google account credentials and click “Allow”.
  5. You should be redirected back to your application.
  6. You should now see a message indicating that you are signed in.

Case 2: Accessing the protected page with a user that is not signed in

  1. Open your browser and navigate to http://localhost:3000/protected.
  2. You should now see a message indicating that you are not signed in.

Conclusion

In this tutorial, we learned how to implement authentication using Next.js and Auth.js. We set up the project, installed Auth.js, and configured it to work with Next.js. We also learned how to set up Prisma, Auth.js, and Next.js to work together. Finally, we implemented authentication in our application by using the auth method to check if the user is authenticated and show unauthenticated content if they’re not.

I hope this tutorial has been helpful in understanding how to implement authentication using Next.js and Auth.js.

What’s next?

In the next tutorial, we will learn how to implement authorization using Next.js and Auth.js. We will use the user object to check if the user has the necessary permissions to access a specific page or resource.

If you have any questions or suggestions, feel free to reach out to me on GitHub or Twitter. Stay tuned for more tutorials on advanced Next.js and Auth.js concepts!