Next.js Authentication With Next-Auth V5 || Google & GitHub
Step-by-step Next-Auth v5 setup in Next.js App Router — Google and GitHub OAuth only. Covers auth.ts, LoginForm, Server Actions, [...nextauth] route, protected pages, session UI, and next.config image patterns.
Introduction
This guide covers only Next.js authentication with Next-Auth v5 using Google and GitHub as OAuth providers. No email/password flows, no other providers — just the two social logins most teams need first.
Next-Auth v5 (next-auth@5, also called Auth.js) gives you one auth.ts file, a catch-all API
route, and auth() for reading sessions in Server Components.
Next.js Authentication (Next-Auth v5)
│
┌─────────┬───────────┼───────────┬─────────┐
│ │ │ │ │
Project Core Config Providers API & Routes UI & UX
Setup (auth.ts) (Google, ([...nextauth] (session
(LoginForm, GitHub) protected) display)
Logout,
Actions)What you will build:
| Branch | What it covers |
|---|---|
| 1. Project setup | LoginForm (Google + GitHub buttons), LogoutButton, Server Actions |
| 2. Core configuration | auth.ts — providers, handlers, auth, signIn, signOut |
| 3. Authentication providers | Google Cloud Console, GitHub OAuth Apps, environment variables |
| 4. API & route handling | [...nextauth] route handler, session verification, login redirect |
| 5. User interface | Display user name and image, next.config.js remote patterns |
Quick index
1. Project setup — components & actions
Install Next-Auth v5 and generate the session encryption secret:
npm install next-auth@5
npx auth secretFolder structure
├── app/
│ ├── api/auth/[...nextauth]/route.ts # GET & POST handlers
│ ├── login/page.tsx # Login page
│ ├── dashboard/page.tsx # Protected example
│ └── page.tsx # Homepage — session UI
├── components/
│ ├── LoginForm.tsx # Google + GitHub buttons
│ └── LogoutButton.tsx
├── actions/
│ └── auth.ts # doSocialLogin, doLogout
├── auth.ts # Next-Auth v5 core config
└── next.config.js # Google/GitHub avatar domainsServer Actions
// actions/auth.ts
'use server'
import { signIn, signOut } from '@/auth'
import { AuthError } from 'next-auth'
export async function doSocialLogin(provider: 'google' | 'github') {
try {
await signIn(provider, { redirectTo: '/dashboard' })
} catch (error) {
if (error instanceof AuthError) throw error
throw error // re-throw NEXT_REDIRECT on success
}
}
export async function doLogout() {
await signOut({ redirectTo: '/login' })
}LoginForm — Google & GitHub buttons
// components/LoginForm.tsx
import { doSocialLogin } from '@/actions/auth'
export function LoginForm() {
return (
<div className="flex flex-col gap-4">
<form action={doSocialLogin.bind(null, 'google')}>
<button type="submit" className="w-full rounded-lg border px-4 py-3 font-semibold">
Continue with Google
</button>
</form>
<form action={doSocialLogin.bind(null, 'github')}>
<button type="submit" className="w-full rounded-lg border px-4 py-3 font-semibold">
Continue with GitHub
</button>
</form>
</div>
)
}LogoutButton
// components/LogoutButton.tsx
import { doLogout } from '@/actions/auth'
export function LogoutButton() {
return (
<form action={doLogout}>
<button type="submit" className="rounded-lg border px-4 py-2 text-sm font-semibold">
Sign out
</button>
</form>
)
}Login page
// app/login/page.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
import { LoginForm } from '@/components/LoginForm'
export default async function LoginPage() {
const session = await auth()
if (session) redirect('/dashboard')
return (
<main className="mx-auto max-w-md px-6 py-20">
<h1 className="text-2xl font-bold">Sign in</h1>
<p className="mt-2 text-neutral-600">Sign in with Google or GitHub.</p>
<div className="mt-8">
<LoginForm />
</div>
</main>
)
}2. Core configuration — auth.ts
auth.ts is the single source of truth for Next-Auth v5. It registers Google and GitHub, then exports everything the app needs.
Exports from auth.ts
| Export | Purpose |
|---|---|
handlers | GET and POST for [...nextauth] route |
auth | Read session in Server Components and route handlers |
signIn | Start Google or GitHub OAuth (used in Server Actions) |
signOut | End the session |
// auth.ts
import NextAuth from 'next-auth'
import Google from 'next-auth/providers/google'
import GitHub from 'next-auth/providers/github'
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
GitHub({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}),
],
pages: {
signIn: '/login',
},
callbacks: {
async session({ session, token }) {
if (session.user && token.sub) {
session.user.id = token.sub
}
return session
},
},
session: {
strategy: 'jwt',
},
})3. Google & GitHub provider setup
OAuth needs credentials from both the provider console and your .env.local file.
Environment variables
# .env.local
AUTH_SECRET=your_generated_secret_here # npx auth secret
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
AUTH_URL=http://localhost:3000 # optional locally; set in production| Variable | Source |
|---|---|
AUTH_SECRET | npx auth secret |
GOOGLE_CLIENT_ID | Google Cloud Console |
GOOGLE_CLIENT_SECRET | Google Cloud Console |
GITHUB_CLIENT_ID | GitHub Developer Settings |
GITHUB_CLIENT_SECRET | GitHub Developer Settings |
Google Cloud Console
- Google Cloud Console → APIs & Services → Credentials
- Create Credentials → OAuth client ID → type Web application
- Add Authorized redirect URIs:
http://localhost:3000/api/auth/callback/google
https://yourdomain.com/api/auth/callback/google- Copy Client ID and Client Secret →
.env.local
GitHub Developer Settings
- GitHub Developer Settings → OAuth Apps → New OAuth App
- Set fields:
| Field | Value |
|---|---|
| Application name | Your app name |
| Homepage URL | http://localhost:3000 |
| Authorization callback URL | http://localhost:3000/api/auth/callback/github |
- Generate a new client secret → copy both values to
.env.local
Google vs GitHub at a glance
| GitHub | ||
|---|---|---|
| Console | Google Cloud Console | GitHub Developer Settings |
| Callback path | /api/auth/callback/google | /api/auth/callback/github |
| Avatar hostname | lh3.googleusercontent.com | avatars.githubusercontent.com |
4. API & route handling
Next-Auth v5 handles the full OAuth flow through one catch-all API route.
[...nextauth] route handler
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'
export const { GET, POST } = handlersNext-Auth registers these routes automatically:
| Route | Purpose |
|---|---|
/api/auth/signin/google | Start Google OAuth |
/api/auth/signin/github | Start GitHub OAuth |
/api/auth/callback/google | Google token exchange |
/api/auth/callback/github | GitHub token exchange |
/api/auth/signout | End session |
/api/auth/session | Return session JSON |
OAuth flow (Google example)
LoginForm → doSocialLogin('google')
↓
signIn('google') [Server Action]
↓
/api/auth/signin/google
↓
Google consent screen
↓
/api/auth/callback/google → session cookie set
↓
Redirect to /dashboardThe GitHub flow is identical — swap google for github and /api/auth/callback/github.
5. Protected routes
After Google or GitHub login, gate private pages with auth() in the Server Component.
// app/dashboard/page.tsx
import { auth } from '@/auth'
import { redirect } from 'next/navigation'
import { LogoutButton } from '@/components/LogoutButton'
export default async function DashboardPage() {
const session = await auth()
if (!session?.user) redirect('/login')
return (
<main className="mx-auto max-w-2xl px-6 py-20">
<h1 className="text-2xl font-bold">Dashboard</h1>
<p className="mt-4">Welcome, {session.user.name}</p>
<div className="mt-8">
<LogoutButton />
</div>
</main>
)
}For API routes that return JSON, verify the session and return 401:
// app/api/me/route.ts
import { auth } from '@/auth'
import { NextResponse } from 'next/server'
export async function GET() {
const session = await auth()
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
return NextResponse.json({
name: session.user.name,
email: session.user.email,
})
}6. User interface & experience
Show the signed-in user's name and profile image on the homepage. Google and GitHub avatars come from external domains — whitelist them in next.config.js.
Homepage — display user name & image
// app/page.tsx
import Image from 'next/image'
import Link from 'next/link'
import { auth } from '@/auth'
import { LogoutButton } from '@/components/LogoutButton'
export default async function HomePage() {
const session = await auth()
return (
<main className="mx-auto max-w-2xl px-6 py-20">
{session?.user ? (
<div className="flex items-center gap-4">
{session.user.image && (
<Image
src={session.user.image}
alt={session.user.name ?? 'User avatar'}
width={48}
height={48}
className="rounded-full"
/>
)}
<div>
<p className="font-semibold">{session.user.name}</p>
<p className="text-sm text-neutral-600">{session.user.email}</p>
</div>
<LogoutButton />
</div>
) : (
<div>
<p>Not signed in.</p>
<Link href="/login" className="mt-4 inline-block font-semibold underline">
Sign in with Google or GitHub
</Link>
</div>
)}
</main>
)
}next.config.js — whitelist Google & GitHub avatars
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'lh3.googleusercontent.com',
pathname: '/**',
},
{
protocol: 'https',
hostname: 'avatars.githubusercontent.com',
pathname: '/**',
},
],
},
}
module.exports = nextConfig| Provider | Avatar hostname |
|---|---|
lh3.googleusercontent.com | |
| GitHub | avatars.githubusercontent.com |
7. Deploy checklist & troubleshooting
Before going live
| Step | Action |
|---|---|
| 1 | Set AUTH_SECRET, GOOGLE_*, and GITHUB_* in production env vars |
| 2 | Add production callback URLs in Google Cloud Console |
| 3 | Add production callback URL in GitHub OAuth App |
| 4 | Set AUTH_URL=https://yourdomain.com |
| 5 | Whitelist both avatar hostnames in next.config.js |
| 6 | Test Google sign-in, GitHub sign-in, sign-out, and /dashboard redirect |
Common errors (Google & GitHub)
| Error | Fix |
|---|---|
redirect_uri_mismatch | Callback must be exactly /api/auth/callback/google or /api/auth/callback/github |
Configuration | Run npx auth secret; verify all six env vars are set |
Invalid src prop on Image | Add lh3.googleusercontent.com and avatars.githubusercontent.com to remotePatterns |
Session null in production | Set AUTH_URL to your exact production URL with https |
Summary
| Mind map branch | Files |
|---|---|
| Project setup | LoginForm, LogoutButton, actions/auth.ts |
| Core configuration | auth.ts — handlers, auth, signIn, signOut |
| Authentication providers | Google Cloud Console, GitHub OAuth Apps, .env.local |
| API & route handling | app/api/auth/[...nextauth]/route.ts, auth() guards |
| UI & experience | Homepage name + image, next.config.js remotePatterns |
That is the complete Next-Auth v5 + Google & GitHub setup for Next.js App Router — nothing else required to ship social login.
Subscribe to my newsletter
Stay up to date and get notified when I share new contents.
No spam ever, unsubscribe anytime