This guide shows a basic approach for adding user authentication with NextAuth and a local SQLite database via Prisma in a Next.js 13+ App Router project.
1. Install Dependencies
npm install next-auth @next-auth/prisma-adapter @prisma/client
npx prisma init --datasource-provider sqlite
This adds NextAuth, the official Prisma adapter for NextAuth, and Prisma Client. The second command sets up a starter prisma/schema.prisma
.
2. Configure Prisma
In your new prisma/schema.prisma
file, set up a local SQLite data source and include the required NextAuth models. Example:
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
// Optional for WebAuthn support
Authenticator Authenticator[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model VerificationToken {
identifier String
token String
expires DateTime
@@unique([identifier, token])
}
// Optional for WebAuthn support
model Authenticator {
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([userId, credentialID])
}
Then run:
npx prisma migrate dev --name init
npx prisma generate
Add this to your .env
file:
DATABASE_URL="file:./db.sqlite"
3. Configure Session (App Router)
Create a file at utils/GetSession.ts
:
import { NextAuthOptions, getServerSession } from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient();
const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
async session({ session, user }) {
if (session.user) {
session.user.id = user.id;
session.user.role = user.role;
}
return session;
},
}
};
/**
* Helper function to get the session on the server without having to import the authOptions object every single time
* @returns The session object or null
*/
const getSession = () => getServerSession(authOptions)
export { authOptions, getSession }
This uses a Google OAuth provider. You can substitute any NextAuth provider(s) you like, or use CredentialsProvider
for a custom username/password.
Make sure you have GOOGLE_CLIENT_ID
and GOOGLE_CLIENT_SECRET
in your .env
if you are using Google. For example:
GOOGLE_CLIENT_ID="123-abc.apps.googleusercontent.com"
GOOGLE_CLIENT_SECRET="XYZ123"
Create a file at api/auth/[...nextauth]/route.ts
:
import { authOptions } from "@/utils/GetSession"
import NextAuth from "next-auth"
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
Create a component for the provider at components/auth/Provider.tsx
:
"use client"
import type { Session } from "next-auth"
import { SessionProvider } from "next-auth/react"
export default function Providers({ session, children }: { session: Session | null, children: React.ReactNode }) {
return (
// @ts-expect-error - Known issue with Auth.js v5 + React 19 type definitions
<SessionProvider session={session}>
{children}
</SessionProvider>
)
}
Finally, at app/layout.tsx
:
import { getSession } from "@/utils/GetSession"
import Providers from "@/components/auth/Provider"
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const session = await getSession()
return (
<html lang="en">
<body>
<Providers session={session}>
{children}
</Providers>
</body>
</html>
)
}
4. Using the Auth in the Client
NextAuth provides the useSession
and signIn
/signOut
client helpers. Example:
'use client';
import { signIn, signOut, useSession } from 'next-auth/react';
export function AuthButton() {
const { data: session } = useSession();
if (session) {
return (
<>
Signed in as {session.user?.email} <br />
<button onClick={() => signOut()}>Sign out</button>
</>
);
}
return (
<>
Not signed in <br />
<button onClick={() => signIn()}>Sign in</button>
</>
);
}