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

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>
  );
}
1
Subscribe to my newsletter

Read articles from Swarnabha Majumder directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Swarnabha Majumder
Swarnabha Majumder