Next.js 14 Authentication using Prisma and Auth.js: A Complete Guide
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:
npx create-next-app@latestThis 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:
npm install next-auth@betanpm install @prisma/client @auth/prisma-adapternpm install prisma --save-dev
# Generate auth secretnpx auth secret
# Set up Prismanpx prisma initIt 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:
.envOpen .env file and modify the following code:
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:
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 supportmodel 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:
# migrate the databasenpm exec prisma migrate dev
# generate the prisma clientnpm exec prisma generateConfiguring Auth.js
Create file named auth.ts in the root of your project and add the following
code:
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:
import { handlers } from "@/auth" // Referring to the auth.ts we just createdexport const { GET, POST } = handlersRegistering 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:
- Go to the Google Cloud Console and create a new project.
- Go to the APIs & Services section and enable the Google Identity and Access Management (IAM) API.
- Go to the Credentials section and create a new OAuth client ID.
- Fill Authorized JavaScript origins with
http://localhost:3000 - Fill Authorized redirect URIs with
http://localhost:3000/api/auth/callback/google- Click Save
- Copy the client ID and client secret and add them to your
.envfile.
AUTH_GOOGLE_ID=YOUR_CLIENT_IDAUTH_GOOGLE_SECRET=YOUR_CLIENT_SECRETImplementing 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.
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.
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
- Open your browser and navigate to
http://localhost:3000/login. - Click the “Signin with Google” button.
- You should be redirected to Google’s login page.
- Enter your Google account credentials and click “Allow”.
- You should be redirected back to your application.
- 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
- Open your browser and navigate to
http://localhost:3000/protected. - 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!