Skip to main content

How to use Prisma ORM with Auth.js and Next.js

25 min

Introduction

Auth.js is a flexible, open-source authentication library designed to simplify adding authentication to your Next.js applications.

In this guide, you'll wire Auth.js into a brand-new Next.js app and persist users in a Prisma Postgres database. You can find a complete example of this guide on GitHub.

Prerequisites

  • Node.js 18+
  • Basic familiarity with Next.js App Router and Prisma

1. Set up your project

Create a new Next.js application:

npx create-next-app@latest authjs-prisma

It will prompt you to customize your setup. Choose the defaults:

info
  • Would you like to use TypeScript? Yes
  • Would you like to use ESLint? Yes
  • Would you like to use Tailwind CSS? Yes
  • Would you like your code inside a src/ directory? No
  • Would you like to use App Router? (recommended) Yes
  • Would you like to use Turbopack for next dev? Yes
  • Would you like to customize the import alias (@/* by default)? No

Navigate to the project directory:

cd authjs-prisma

2. Install and configure Prisma

2.1. Install dependencies

To get started with Prisma, you'll need to install a few dependencies:

npm install prisma tsx --save-dev
npm install @prisma/extension-accelerate @prisma/client

Once installed, initialize Prisma in your project:

npx prisma init --db --output ../app/generated/prisma
info

You'll need to answer a few questions while setting up your Prisma Postgres database. Select the region closest to your location and a memorable name for your database like "My Auth.js Project"

This will create:

  • A prisma directory with a schema.prisma file.
  • A Prisma Postgres database.
  • A .env file containing the DATABASE_URL at the project root.
  • A schema configuration that specifies where the Prisma Client will be generated (../app/generated/prisma).

2.2. Define your Prisma Schema

In the prisma/schema.prisma file, swap the provider to prisma-client and add the runtime edge-light to the generator:

prisma/schema.prisma
generator client {
provider = "prisma-client"
output = "../app/generated/prisma"
runtime = "edge-light"
}

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

Add the following models to the schema.prisma file, these models are provided by Auth.js:

prisma/schema.prisma
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[]

@@map("users")
}

model VerificationToken {
identifier String
token String
expires DateTime

@@unique([identifier, token])
@@map("verification_tokens")
}

This creates the following models:

  • Account: Stores OAuth provider information (access tokens, refresh tokens, provider account IDs) and enables users to sign in with multiple providers while maintaining a single user record.

  • Session: Tracks authenticated user sessions with a unique session token, user ID, and expiration time to maintain authentication state across requests.

  • User: The core model storing user information (name, email, profile image). Users can have multiple accounts from different providers and multiple active sessions.

  • VerificationToken: Stores temporary tokens for email verification, password reset, and other security operations with expiration times.

2.3. Configure the Prisma Client generator

Now, run the following command to create the database tables and generate the Prisma Client:

npx prisma migrate dev --name init

2.4 Create a Prisma Client

Create a new folder in the root called lib and create a new file called prisma.ts in it. This file will contain the Prisma Client:

lib/prisma.ts
import { PrismaClient } from '../app/generated/prisma'
import { withAccelerate } from '@prisma/extension-accelerate'

const globalForPrisma = global as unknown as {
prisma: PrismaClient
}

const prisma = globalForPrisma.prisma || new PrismaClient().$extends(withAccelerate())

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

export default prisma

3. Set up Auth.js credentials

3.1. Install dependencies

Install the Auth.js dependencies:

npm install @auth/prisma-adapter next-auth@beta

3.2 Credentials

For this guide, you'll be setting up OAuth with Github. For this, you'll need 3 environment variables:

  • AUTH_SECRET - Provided by Auth.js
  • CLIENT_ID - Provided by Github
  • CLIENT_SECRET - Provided by Github

To get the AUTH_SECRET, you can run the following command:

npx auth secret --copy
  • --copy will copy the secret to your clipboard. (Normally, just running npx auth secret will add the secret to your .env.local file. To keep it tidy, you can use --copy and add it to the .env file that Prisma created earlier.)

Add the following to the .env file:

.env
DATABASE_URL=<YOUR_DATABASE_URL>
AUTH_SECRET=<YOUR_AUTH_SECRET>

To get the CLIENT_ID and CLIENT_SECRET, you can create a new OAuth application on Github.

  1. Navigate to Github Developer Settings
  2. Click on New OAuth App
  3. Enter a name for your app, a home page URL, and a callback URL
  • Name: Auth.js + Prisma (Or anything you want)
  • Homepage URL: http://localhost:3000
  • Callback URL: http://localhost:3000/api/auth/callback/github
  1. Click Register application
  2. Click Generate new client secret and copy the Client ID and Client Secret.
  3. Add the Client ID and Client Secret to the .env file:
.env
DATABASE_URL=<YOUR_DATABASE_URL>
AUTH_SECRET=<YOUR_AUTH_SECRET>
AUTH_GITHUB_ID=<YOUR_GITHUB_CLIENT_ID>
AUTH_GITHUB_SECRET=<YOUR_GITHUB_CLIENT_SECRET>

3.3. Configure Auth.js

In the /lib folder, create a new file called auth.ts and add the following code:

lib/auth.ts
import NextAuth from 'next-auth'

export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [],
})

Next, you'll need to add the Github provider to the auth.ts file:

lib/auth.ts
import NextAuth from 'next-auth'
import GitHub from 'next-auth/providers/github'

export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [GitHub],
})

Users will now be able to sign in with Github. To add them to your database, you'll need to use the Prisma Adapter:

lib/auth.ts
import NextAuth from 'next-auth'
import { PrismaAdapter } from '@auth/prisma-adapter'
import prisma from '@/lib/prisma'
import GitHub from 'next-auth/providers/github'

export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [GitHub],
})

In the root, create a new file called middleware.ts. This will protect your routes and ensure that only authenticated users can access them:

middleware.ts
export { auth as middleware } from '@/lib/auth'

3.4. Configure the Route

The route handler is required to handle authentication requests from Auth.js. It exports the GET and POST handlers that Auth.js uses for sign-in, sign-out, and callback operations.

Create a new file at app/api/auth/[...nextauth]/route.ts:

mkdir -p app/api/auth/[...nextauth]
touch app/api/auth/[...nextauth]/route.ts

Add the following code to the file:

app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/lib/auth'

export const { GET, POST } = handlers

That's it! Your app is now secured. To see more configuration options, check out the Auth.js Middleware documentation.

4. Auth components

You will be creating a Sign In and Sign Out button. Create a /components folder in the root and add a new file called auth-components.tsx in it.

Start by importing the signIn and signOut functions from the auth file:

components/auth-components.tsx
import { signIn, signOut } from "@/lib/auth"

Next, create the SignIn and SignOut components:

components/auth-components.tsx
import { signIn, signOut } from "@/lib/auth"

export function SignIn({ provider }: { provider?: string }) {
return (
<form>
<button className="bg-neutral-700 text-white p-2 rounded-md">
Sign In with {provider}
</button>
</form>
)
}

export function SignOut() {
return (
<form>
<button className="bg-neutral-700 text-white p-2 rounded-md">
Sign Out
</button>
</form>
)
}

To add functionality to both of the buttons, add an action to the form that calls the signIn and signOut functions respectively:

components/auth-components.tsx
import { signIn, signOut } from "@/lib/auth"

export function SignIn({ provider }: { provider?: string }) {
return (
<form
action={async () => {
"use server"
await signIn(provider)
}}
>
<button className="bg-neutral-700 text-white p-2 rounded-md">
Sign In with {provider}
</button>
</form>
)
}

export function SignOut() {
return (
<form
action={async () => {
"use server"
await signOut()
}}
className="w-full"
>
<button className="bg-neutral-700 text-white p-2 rounded-md">
Sign Out
</button>
</form>
)
}

5. Add the components to your app

5.1. Set up the basic page structure

In the /app folder, replace the page.tsx file with the following code:

app/page.tsx
const Page = async () => {
return (
<div className="min-h-screen bg-black flex items-center justify-center p-4">
<div className="bg-neutral-800 rounded-lg p-6 max-w-xl w-full">
<h1 className="text-white text-xl mb-4 text-center">Auth.js + Prisma</h1>
</div>
</div>
);
};

export default Page;

5.2. Add imports and authentication check

Import the required components and add session checking:

app/page.tsx
import { SignIn, SignOut } from "@/components/auth-components";
import { auth } from "@/lib/auth";

const Page = async () => {
const session = await auth();

return (
<div className="min-h-screen bg-black flex items-center justify-center p-4">
<div className="bg-neutral-800 rounded-lg p-6 max-w-xl w-full">
<h1 className="text-white text-xl mb-4 text-center">Auth.js + Prisma</h1>
</div>
</div>
);
};

export default Page;

5.3. Show content based on auth state

Add the logic to show different content based on whether the user is signed in:

app/page.tsx
import { SignIn, SignOut } from "@/components/auth-components";
import { auth } from "@/lib/auth";

const Page = async () => {
const session = await auth();

return (
<div className="min-h-screen bg-black flex items-center justify-center p-4">
<div className="bg-neutral-800 rounded-lg p-6 max-w-xl w-full">
<h1 className="text-white text-xl mb-4 text-center">Auth.js + Prisma</h1>

{!session ? (
<div className="text-center">
<SignIn provider="github" />
</div>
) : (
<div className="space-y-4">
<div className="text-center">
<p className="text-gray-300">Signed in as:</p>
<p className="text-white">{session.user?.email}</p>
</div>

<div className="text-center">
<p className="text-gray-300">Data fetched from DB with Prisma:</p>
</div>

<div className="text-center">
<SignOut />
</div>
</div>
)}
</div>
</div>
);
};

export default Page;

5.4. Add the user data to the page

If the user is signed in, you can fetch the user data from the database and display it on the page.

app/page.tsx
import { SignIn, SignOut } from "@/components/auth-components";
import { auth } from "@/lib/auth";
import prisma from "@/lib/prisma";

const Page = async () => {
const session = await auth();
let user = null;

if (session) {
user = await prisma.user.findUnique({
where: {
id: session.user?.id,
}
});
}

return (
<div className="min-h-screen bg-black flex items-center justify-center p-4">
<div className="bg-neutral-800 rounded-lg p-6 max-w-xl w-full">
<h1 className="text-white text-xl mb-4 text-center">Auth.js + Prisma</h1>

{!session ? (
<div className="text-center">
<SignIn provider="github" />
</div>
) : (
<div className="space-y-4">
<div className="text-center">
<p className="text-gray-300">Signed in as:</p>
<p className="text-white">{session.user?.email}</p>
</div>

<div className="text-center">
<p className="text-gray-300">Data fetched from DB with Prisma:</p>
</div>

<div className="bg-neutral-900 rounded p-3">
<pre className="text-xs text-gray-300">
{JSON.stringify(user, null, 2)}
</pre>
</div>

<div className="text-center">
<SignOut />
</div>
</div>
)}
</div>
</div>
);
};

export default Page;

6. Test it out

warning

Before starting the development server, note that if you are using Next.js v15.2.0 or v15.2.1, do not use Turbopack as there is a known issue. Remove Turbopack from your dev script by updating your package.json

package.json
"script":{
"dev": "next dev --turbopack",
"dev": "next dev",
}

This change is not needed on any versions before or after.

Your application is now fully configured.

  1. Start the development server to test it:
npm run dev
  1. Navigate to http://localhost:3000 in your browser. You should see the home page with a "Sign In with github" button.

  2. Click on Sign In with github, authorize the app, and you should be redirected to the dashboard. You can then sign out and sign back in.

  3. To view the user data directly in your database, you can use Prisma Studio:

npx prisma studio
  1. This will open a new tab in your browser where you can see the User, Session, and Account tables and their contents.
success

Congratulations! You now have a fully functional authentication system built with Auth.js, Prisma, and Next.js.


Stay connected with Prisma

Continue your Prisma journey by connecting with our active community. Stay informed, get involved, and collaborate with other developers:

We genuinely value your involvement and look forward to having you as part of our community!