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@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:
npm install next-auth@betanpm install @prisma/client @auth/prisma-adapternpm install prisma --save-dev
# Generate auth secretnpx auth secret
# Set up Prismanpx 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:
.env
Open .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 generate
Configuring 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 } = 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:
- 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
.env
file.
AUTH_GOOGLE_ID=YOUR_CLIENT_IDAUTH_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.
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!