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.
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.
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-adapter
import 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 }
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> }
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[]
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 } })
Day 5 is ready when you are.
Want live instruction and hands-on projects? Join the AI bootcamp — 3 days, 5 cities.
The foundations from today carry directly into Day 5. In the next session the focus shifts to Day 5 — building directly on everything covered here.
Before moving on, verify you can answer these without looking:
Live Bootcamp
Learn this in person — 2 days, 5 cities
Thu–Fri sessions in Denver, Los Angeles, New York, Chicago, and Dallas. $1,490 per seat. June–October 2026.
Reserve Your Seat →