A Next.js app with working GitHub OAuth login, a protected dashboard page that shows user info, and a database-backed session so users stay logged in across refreshes.
Install and Configure NextAuth.js
NextAuth.js (now Auth.js) handles OAuth, JWT sessions, database sessions, and credential auth. It integrates directly with Next.js and requires minimal configuration for common providers.
npm install next-auth @auth/prisma-adapterimport NextAuth from 'next-auth'
import GithubProvider from 'next-auth/providers/github'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'
const handler = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!,
})
],
callbacks: {
session({ session, user }) {
session.user.id = user.id // add user ID to session
return session
}
}
})
export { handler as GET, handler as POST }Get Session in Server and Client Components
Use getServerSession() in Server Components and useSession() in Client Components. Wrap your app in SessionProvider to make client-side session available everywhere.
import { getServerSession } from 'next-auth'
import { redirect } from 'next/navigation'
export default async function Dashboard() {
const session = await getServerSession()
if (!session) {
redirect('/api/auth/signin')
}
return (
<div>
<h1>Welcome, {session.user?.name}</h1>
<img src={session.user?.image ?? ''} alt="avatar" />
</div>
)
}'use client'
import { useSession, signIn, signOut } from 'next-auth/react'
export default function NavUser() {
const { data: session, status } = useSession()
if (status === 'loading') return <span>Loading...</span>
if (session) return <button onClick={() => signOut()}>Sign Out</button>
return <button onClick={() => signIn('github')}>Sign In</button>
}Add Auth Models to Prisma Schema
The Prisma Adapter stores sessions and accounts in your database. Add the required models to your schema.
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
access_token String?
expires_at Int?
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)
}
// Update User model to add:
// accounts Account[]
// sessions Session[]Credential Authentication
For email/password login, use the Credentials provider. Always hash passwords — never store them in plaintext.
import CredentialsProvider from 'next-auth/providers/credentials'
import bcrypt from 'bcryptjs'
CredentialsProvider({
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 }
})
if (!user?.password) return null
const valid = await bcrypt.compare(credentials.password, user.password)
return valid ? user : null
}
})What You Learned Today
- Configured NextAuth.js with GitHub OAuth and the Prisma database adapter
- Retrieved session data in Server Components with getServerSession and Client Components with useSession
- Added the Account and Session Prisma models required by the NextAuth adapter
- Implemented credential authentication with bcrypt password hashing
Go Further on Your Own
- Add Google OAuth provider alongside GitHub — users can sign in with either
- Build a /register page that creates a new user with a hashed password
- Add role-based authorization: admin users can access /admin, regular users cannot
Nice work. Keep going.
Day 5 is ready when you are.
Continue to Day 5Want live instruction and hands-on projects? Join the AI bootcamp — 3 days, 5 cities.