Integrating Auth.js with GitHub OAuth and Prisma ORM in Next.js: A Complete Guide

When I started integrating authentication into my Next.js project, I thought it would be straightforward. After all, Auth.js (formerly NextAuth.js) is supposed to make authentication simple, right? Well, let me save you from the debugging marathon I went through by sharing everything I learned about setting up Auth.js v6 with GitHub OAuth and Prisma ORM.
Prerequisites
A Next.js project (App Router)
Basic knowledge of React and TypeScript
A GitHub account for OAuth setup
A database (PostgreSQL, MySQL, or SQLite)
Step 1: Installing Auth.js and Dependencies
npm install next-auth@beta @auth/prisma-adapter
# run the below command to generate auth secret
npx auth secret
# Core Prisma packages
npm install prisma @prisma/client
Step 2: Setting Up GitHub OAuth
Creating the GitHub OAuth App
Go to GitHub Settings → Developer settings → OAuth Apps
Click "New OAuth App"
Fill in the details:
Application name: Your app name
Homepage URL:
http://localhost:3000
(for development)Authorization callback URL:
http://localhost:3000/api/auth/callback/github
Save the Client ID and Client Secret
Environment Variables
DATABASE_URL="your-database-connection-string"
AUTH_SECRET="your-generated-secret" (generated when npx auth secret was run)
GITHUB_ID="your-github-client-id"
GITHUB_SECRET="your-github-client-secret"
Step 2: Prisma Initialization and Schema Setup
Initialize Prisma
npx prisma init
The Complete Schema
// schema.prisma
generator client {
provider = "prisma-client-js"
output = "../src/generated/prisma"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
name String?
email String @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
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)
@@id([provider, providerAccountId])
}
model Session {
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
@@id([identifier, token])
}
Run the commands
// generate the generated folder
npx prisma generate
// this command is used to sync your local db schema with remote database(eg. neondb) you are using
npx prisma db push
Step 4: The Prisma Client Setup (Critical Step)
The Right Way (Prisma v6+)
import { PrismaClient } from "../generated/prisma";
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma = globalForPrisma.prisma || new PrismaClient();
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
Step 5: Auth.js Configuration
Create lib/auth.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/lib/prisma";
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
GitHub({
clientId: process.env.AUTH_GITHUB_ID!,
clientSecret: process.env.AUTH_GITHUB_SECRET!,
}),
],
callbacks: {
session({ session, token }) {
if (token?.sub && session?.user) {
session.user.id = token.sub;
}
return session;
},
},
});
Step 6: API Route Handler
Create the API route at app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/lib/auth";
export const { GET, POST } = handlers;
Step 7: Session Provider Setup
For client-side session management, wrap your app with the session provider. Create components/auth/session.tsx
:
"use client";
import React from "react";
import { SessionProvider } from "next-auth/react";
export const SessionWrapper = ({ children }: { children: React.ReactNode }) => {
return <SessionProvider>{children}</SessionProvider>;
};
Update your app/layout.tsx
:
import { SessionWrapper } from "@/components/auth/session";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<SessionWrapper>
<html lang="en">
<body>
{children}
</body>
</html>
</SessionWrapper>
);
}
Step 8: Middleware
Create middleware.ts
in root level:
export { auth as middleware } from "@/lib/auth";
export const config = {
matcher: [
"/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
],
};
Step 9: Using Authentication in Your App
Client-Side Usage
"use client"
import { useSession, signIn, signOut } from "next-auth/react"
export function AuthButton() {
const { data: session, status } = useSession()
if (status === "loading") return <p>Loading...</p>
if (session) {
return (
<div>
<p>Signed in as {session.user?.email}</p>
<button onClick={() => signOut()}>Sign out</button>
</div>
)
}
return (
<button onClick={() => signIn("github")}>
Sign in with GitHub
</button>
)
}
Server-Side Usage
import { Button } from "@/components/ui/button";
import { auth, signIn, signOut } from "@/lib/auth";
export default async function Home() {
const session = await auth();
return (
<div>
<h1 className="text-3xl font-bold">Home</h1>
{session?.user ? (
<>
<p>Hello {session.user.name}</p>
<form
action={async () => {
"use server";
await signOut();
}}
>
<Button>Sign out</Button>
</form>
</>
) : (
<form
action={async () => {
"use server";
await signIn("github", { callbackUrl: "/" });
}}
>
<Button>Signin with Github</Button>
</form>
)}
</div>
);
}
Subscribe to my newsletter
Read articles from Swarnabha Majumder directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
