Database Adapter Configuration
Complete guide for using database sessions with Auth.js 5 and Next.js App Router.
Prisma Setup
1. Install Dependencies
npm install @auth/prisma-adapter prisma @prisma/client
npm install -D prisma
2. Prisma Schema
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql" // or "mysql" | "sqlite"
url = env("DATABASE_URL")
}
// NextAuth.js models
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?
password String? // For credentials provider
role String @default("user")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
accounts Account[]
sessions Session[]
@@map("users")
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
@@map("verificationtokens")
}
3. Generate Prisma Client
npx prisma generate
npx prisma db push
4. Prisma Client Singleton
// lib/prisma.ts
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
5. Auth Configuration with Prisma Adapter
// 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";
import Credentials from "next-auth/providers/credentials";
import bcrypt from "bcryptjs";
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
session: {
strategy: "database", // Use database sessions
maxAge: 30 * 24 * 60 * 60, // 30 days
updateAge: 24 * 60 * 60, // 24 hours
},
providers: [
GitHub({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
Credentials({
name: "credentials",
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null;
}
const user = await prisma.user.findUnique({
where: { email: credentials.email as string },
});
if (!user || !user.password) {
return null;
}
const isValid = await bcrypt.compare(
credentials.password as string,
user.password
);
if (!isValid) {
return null;
}
return {
id: user.id,
email: user.email,
name: user.name,
image: user.image,
role: user.role,
};
},
}),
],
callbacks: {
async session({ session, user }) {
// With database sessions, user is the database user
if (session.user) {
session.user.id = user.id;
session.user.role = (user as any).role;
}
return session;
},
},
});
Drizzle ORM Setup
1. Install Dependencies
npm install @auth/drizzle-adapter drizzle-orm
npm install -D drizzle-kit
2. Database Schema
// lib/db/schema.ts
import {
pgTable,
text,
timestamp,
primaryKey,
integer,
uuid,
} from "drizzle-orm/pg-core";
import type { AdapterAccount } from "next-auth/adapters";
export const users = pgTable("user", {
id: text("id")
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
name: text("name"),
email: text("email").notNull().unique(),
emailVerified: timestamp("emailVerified", { mode: "date" }),
image: text("image"),
password: text("password"),
role: text("role").default("user"),
createdAt: timestamp("createdAt", { mode: "date" }).defaultNow(),
updatedAt: timestamp("updatedAt", { mode: "date" }).defaultNow(),
});
export const accounts = pgTable(
"account",
{
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
type: text("type").$type<AdapterAccount["type"]>().notNull(),
provider: text("provider").notNull(),
providerAccountId: text("providerAccountId").notNull(),
refresh_token: text("refresh_token"),
access_token: text("access_token"),
expires_at: integer("expires_at"),
token_type: text("token_type"),
scope: text("scope"),
id_token: text("id_token"),
session_state: text("session_state"),
},
(account) => ({
compoundKey: primaryKey({
columns: [account.provider, account.providerAccountId],
}),
})
);
export const sessions = pgTable("session", {
sessionToken: text("sessionToken").notNull().primaryKey(),
userId: text("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
expires: timestamp("expires", { mode: "date" }).notNull(),
});
export const verificationTokens = pgTable(
"verificationToken",
{
identifier: text("identifier").notNull(),
token: text("token").notNull(),
expires: timestamp("expires", { mode: "date" }).notNull(),
},
(vt) => ({
compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }),
})
);
3. Drizzle Client
// lib/db/index.ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
export const db = drizzle(pool, { schema });
4. Auth Configuration with Drizzle Adapter
// auth.ts
import NextAuth from "next-auth";
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { db } from "@/lib/db";
import * as schema from "@/lib/db/schema";
import GitHub from "next-auth/providers/github";
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: DrizzleAdapter(db, {
usersTable: schema.users,
accountsTable: schema.accounts,
sessionsTable: schema.sessions,
verificationTokensTable: schema.verificationTokens,
}),
session: {
strategy: "database",
},
providers: [
GitHub({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
}),
],
});
Custom Adapter
For custom database or cache implementations:
// lib/auth/custom-adapter.ts
import type {
Adapter,
AdapterUser,
AdapterAccount,
AdapterSession,
VerificationToken,
} from "next-auth/adapters";
export function CustomAdapter(db: any): Adapter {
return {
async createUser(user) {
const created = await db.user.create({
data: {
email: user.email,
name: user.name,
image: user.image,
emailVerified: user.emailVerified,
},
});
return created;
},
async getUser(id) {
return await db.user.findUnique({ where: { id } });
},
async getUserByEmail(email) {
return await db.user.findUnique({ where: { email } });
},
async getUserByAccount({ providerAccountId, provider }) {
const account = await db.account.findFirst({
where: { providerAccountId, provider },
include: { user: true },
});
return account?.user ?? null;
},
async updateUser(user) {
return await db.user.update({
where: { id: user.id },
data: user,
});
},
async linkAccount(account) {
await db.account.create({
data: {
userId: account.userId,
type: account.type,
provider: account.provider,
providerAccountId: account.providerAccountId,
refresh_token: account.refresh_token,
access_token: account.access_token,
expires_at: account.expires_at,
token_type: account.token_type,
scope: account.scope,
id_token: account.id_token,
session_state: account.session_state,
},
});
return account;
},
async createSession(session) {
return await db.session.create({
data: {
sessionToken: session.sessionToken,
userId: session.userId,
expires: session.expires,
},
});
},
async getSessionAndUser(sessionToken) {
const session = await db.session.findUnique({
where: { sessionToken },
include: { user: true },
});
if (!session) return null;
return {
session: {
sessionToken: session.sessionToken,
userId: session.userId,
expires: session.expires,
},
user: session.user,
};
},
async updateSession(session) {
return await db.session.update({
where: { sessionToken: session.sessionToken },
data: session,
});
},
async deleteSession(sessionToken) {
await db.session.delete({ where: { sessionToken } });
},
async createVerificationToken(token) {
await db.verificationToken.create({
data: {
identifier: token.identifier,
token: token.token,
expires: token.expires,
},
});
return token;
},
async useVerificationToken({ identifier, token }) {
try {
const deleted = await db.verificationToken.delete({
where: {
identifier_token: { identifier, token },
},
});
return deleted;
} catch {
return null;
}
},
};
}
User Registration with Database
// app/actions/register.ts
"use server";
import { prisma } from "@/lib/prisma";
import { hashPassword } from "@/lib/auth";
import { redirect } from "next/navigation";
export async function registerUser(formData: FormData) {
const email = formData.get("email") as string;
const password = formData.get("password") as string;
const name = formData.get("name") as string;
// Check if user exists
const existingUser = await prisma.user.findUnique({
where: { email },
});
if (existingUser) {
return { error: "User already exists" };
}
// Hash password
const hashedPassword = await hashPassword(password);
// Create user
const user = await prisma.user.create({
data: {
email,
password: hashedPassword,
name,
},
});
redirect("/login");
}
Session Management
List User Sessions
// app/actions/sessions.ts
"use server";
import { prisma } from "@/lib/prisma";
import { auth } from "@/auth";
export async function getUserSessions() {
const session = await auth();
if (!session?.user?.id) {
throw new Error("Unauthorized");
}
const sessions = await prisma.session.findMany({
where: { userId: session.user.id },
orderBy: { expires: "desc" },
});
return sessions;
}
export async function revokeSession(sessionToken: string) {
const session = await auth();
if (!session?.user?.id) {
throw new Error("Unauthorized");
}
// Verify the session belongs to the user
const targetSession = await prisma.session.findUnique({
where: { sessionToken },
});
if (targetSession?.userId !== session.user.id) {
throw new Error("Unauthorized");
}
await prisma.session.delete({
where: { sessionToken },
});
return { success: true };
}
Environment Variables
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
# or
DATABASE_URL="mysql://user:password@localhost:3306/mydb"
# or
DATABASE_URL="file:./dev.db" # SQLite
# NextAuth
AUTH_SECRET="your-secret-key"
AUTH_URL="http://localhost:3000"
# OAuth Providers
GITHUB_ID="your-github-id"
GITHUB_SECRET="your-github-secret"