Kazi Rahamatullah
Kazi Rahamatullah
AboutProjectsBlogContact
UsesBooks
ResumeView CV
Kazi Rahamatullah

© Copyright 2026 Kazi Rahamatullah

AboutProjectsBlogBooksUses
Twitter/XGitHubProduct HuntCodeSandbox
Back to Blog
Next.jsNext-AuthGoogleGitHubOAuthApp Router

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.

Jul 6, 202611 min read

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.

plaintext
          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:

BranchWhat it covers
1. Project setupLoginForm (Google + GitHub buttons), LogoutButton, Server Actions
2. Core configurationauth.ts — providers, handlers, auth, signIn, signOut
3. Authentication providersGoogle Cloud Console, GitHub OAuth Apps, environment variables
4. API & route handling[...nextauth] route handler, session verification, login redirect
5. User interfaceDisplay user name and image, next.config.js remote patterns

Note

Next-Auth v5 installs as next-auth@5 on npm. The v4 pages/api/auth/[...nextauth].ts pattern is replaced by auth.ts + App Router route.ts. This guide uses Next.js App Router only.

Quick index

#Section
1Project setup — components & actions
2Core configuration — auth.ts
3Google & GitHub provider setup
4API & route handling
5Protected routes
6User interface & experience
7Deploy checklist & troubleshooting

1. Project setup — components & actions

Install Next-Auth v5 and generate the session encryption secret:

bash
npm install next-auth@5
npx auth secret

Folder structure

plaintext
├── 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 domains

Server Actions

ts
// 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' })
}

Warning

signIn() throws a NEXT_REDIRECT on success inside Server Actions — that is expected, not a bug. Only catch AuthError for real failures like a misconfigured Google or GitHub client.

LoginForm — Google & GitHub buttons

tsx
// 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

tsx
// 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

tsx
// 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>
  )
}

Tip

Keep auth.ts at the project root or inside src/ — same level as app/. Server Actions are the recommended way to trigger Google and GitHub OAuth from login buttons without exposing secrets to the client.

Back to index


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

ExportPurpose
handlersGET and POST for [...nextauth] route
authRead session in Server Components and route handlers
signInStart Google or GitHub OAuth (used in Server Actions)
signOutEnd the session
ts
// 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',
  },
})

Note

For Google + GitHub OAuth with no user database, strategy: 'jwt' is the right default. Session data is stored in an encrypted httpOnly cookie — no Postgres or MongoDB required.

Back to index


3. Google & GitHub provider setup

OAuth needs credentials from both the provider console and your .env.local file.

Environment variables

bash
# .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
VariableSource
AUTH_SECRETnpx auth secret
GOOGLE_CLIENT_IDGoogle Cloud Console
GOOGLE_CLIENT_SECRETGoogle Cloud Console
GITHUB_CLIENT_IDGitHub Developer Settings
GITHUB_CLIENT_SECRETGitHub Developer Settings

Warning

Never prefix GOOGLE_CLIENT_SECRET or GITHUB_CLIENT_SECRET with NEXT_PUBLIC_. Client secrets must stay server-only.

Google Cloud Console

  1. Google Cloud Console → APIs & Services → Credentials
  2. Create Credentials → OAuth client ID → type Web application
  3. Add Authorized redirect URIs:
plaintext
http://localhost:3000/api/auth/callback/google
https://yourdomain.com/api/auth/callback/google
  1. Copy Client ID and Client Secret → .env.local

Note

First-time setup requires an OAuth consent screen (app name, support email). Add yourself as a test user during development.

GitHub Developer Settings

  1. GitHub Developer Settings → OAuth Apps → New OAuth App
  2. Set fields:
FieldValue
Application nameYour app name
Homepage URLhttp://localhost:3000
Authorization callback URLhttp://localhost:3000/api/auth/callback/github
  1. Generate a new client secret → copy both values to .env.local

Tip

Next-Auth callback URLs always follow {AUTH_URL}/api/auth/callback/{provider}:

  • Google → /api/auth/callback/google
  • GitHub → /api/auth/callback/github

A mismatch here causes redirect_uri_mismatch — the most common setup error.

Google vs GitHub at a glance

GoogleGitHub
ConsoleGoogle Cloud ConsoleGitHub Developer Settings
Callback path/api/auth/callback/google/api/auth/callback/github
Avatar hostnamelh3.googleusercontent.comavatars.githubusercontent.com

Back to index


4. API & route handling

Next-Auth v5 handles the full OAuth flow through one catch-all API route.

[...nextauth] route handler

ts
// app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth'
 
export const { GET, POST } = handlers

Next-Auth registers these routes automatically:

RoutePurpose
/api/auth/signin/googleStart Google OAuth
/api/auth/signin/githubStart GitHub OAuth
/api/auth/callback/googleGoogle token exchange
/api/auth/callback/githubGitHub token exchange
/api/auth/signoutEnd session
/api/auth/sessionReturn session JSON

Note

You never write callback logic yourself. Clicking "Continue with Google" → Next-Auth redirects to Google → user approves → Google hits /api/auth/callback/google → Next-Auth sets the session cookie → user lands on your app.

OAuth flow (Google example)

plaintext
LoginForm → doSocialLogin('google')
      ↓
signIn('google')  [Server Action]
      ↓
/api/auth/signin/google
      ↓
Google consent screen
      ↓
/api/auth/callback/google  →  session cookie set
      ↓
Redirect to /dashboard

The GitHub flow is identical — swap google for github and /api/auth/callback/github.

Back to index


5. Protected routes

After Google or GitHub login, gate private pages with auth() in the Server Component.

tsx
// 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:

ts
// 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,
  })
}

Note

For centralized route protection across many paths (e.g. all /dashboard/* routes), split config into auth.config.ts and wire it through proxy.ts. See Next.js Middleware & Proxy — that is optional and separate from the Google/GitHub setup above.

Back to index


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

tsx
// 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

js
// 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
ProviderAvatar hostname
Googlelh3.googleusercontent.com
GitHubavatars.githubusercontent.com

Warning

Without these remotePatterns, next/image throws an Invalid src prop error when rendering Google or GitHub profile photos. Add both hostnames before deploying.

Tip

Next-Auth maps session.user.image directly from the Google/GitHub profile — no extra API call needed. session.user.name and session.user.email are populated the same way.

Back to index


7. Deploy checklist & troubleshooting

Before going live

StepAction
1Set AUTH_SECRET, GOOGLE_*, and GITHUB_* in production env vars
2Add production callback URLs in Google Cloud Console
3Add production callback URL in GitHub OAuth App
4Set AUTH_URL=https://yourdomain.com
5Whitelist both avatar hostnames in next.config.js
6Test Google sign-in, GitHub sign-in, sign-out, and /dashboard redirect

Common errors (Google & GitHub)

ErrorFix
redirect_uri_mismatchCallback must be exactly /api/auth/callback/google or /api/auth/callback/github
ConfigurationRun npx auth secret; verify all six env vars are set
Invalid src prop on ImageAdd lh3.googleusercontent.com and avatars.githubusercontent.com to remotePatterns
Session null in productionSet AUTH_URL to your exact production URL with https

Performance

JWT sessions (the default here) avoid a database lookup on every request. Google and GitHub OAuth with Next-Auth v5 is lightweight — suitable for marketing sites, dashboards, and internal tools without a user table.

Interview Answer

"How do you add Google and GitHub login with Next-Auth v5 in Next.js?"

Install next-auth@5, create auth.ts with Google and GitHub providers, export handlers to app/api/auth/[...nextauth]/route.ts, register OAuth apps with callbacks at /api/auth/callback/google and /api/auth/callback/github, store credentials in env vars, use Server Actions with signIn('google') / signIn('github') in LoginForm, protect pages with auth() + redirect(), and whitelist both avatar hostnames in next.config.js.

Back to index


Summary

Mind map branchFiles
Project setupLoginForm, LogoutButton, actions/auth.ts
Core configurationauth.ts — handlers, auth, signIn, signOut
Authentication providersGoogle Cloud Console, GitHub OAuth Apps, .env.local
API & route handlingapp/api/auth/[...nextauth]/route.ts, auth() guards
UI & experienceHomepage 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.

Share this article

XLinkedInFacebook
Kazi Rahamatullah

Written by

Kazi Rahamatullah

FullStack Developer

X / TwitterGitHubLinkedIn

Subscribe to my newsletter

Stay up to date and get notified when I share new contents.

No spam ever, unsubscribe anytime