Next.js Authentication

Authentication is how your app verifies who a user is. It answers the question: "Are you who you say you are?" Once verified, authorization answers: "What are you allowed to do?" Next.js does not have authentication built in, but it integrates cleanly with popular libraries that handle the heavy lifting.

Authentication Concepts

AUTHENTICATION  →  Verify identity ("Are you Alice?")
                   Login with email/password, Google, GitHub, etc.

AUTHORIZATION   →  Verify permission ("Can Alice access /admin?")
                   Check role, ownership, or subscription level

SESSION         →  Proof of login stored after authentication
                   Stored in a cookie or database, expires after time

TOKEN           →  A signed string proving identity
                   JWT (JSON Web Token) is the most common type

The Recommended Library: NextAuth.js (Auth.js)

NextAuth.js — now called Auth.js — is the most popular authentication library for Next.js. It handles sessions, cookies, database connections, and OAuth providers like Google and GitHub with minimal code.

npm install next-auth@beta

Setting Up Auth.js

Create an auth.js file at the root of your project to configure providers and options.

// auth.js
import NextAuth from 'next-auth';
import GitHub from 'next-auth/providers/github';
import Google from 'next-auth/providers/google';

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    GitHub({
      clientId: process.env.GITHUB_CLIENT_ID,
      clientSecret: process.env.GITHUB_CLIENT_SECRET,
    }),
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
  ],
});

Then create the API route that handles all authentication requests:

// app/api/auth/[...nextauth]/route.js
import { handlers } from '@/auth';

export const { GET, POST } = handlers;

Diagram: OAuth Login Flow

USER CLICKS "Sign in with Google"
          ↓
Next.js redirects to Google's login page
          ↓
User enters Google credentials on Google's site
          ↓
Google redirects back to your app with a code
          ↓
Auth.js exchanges code for user info (name, email, photo)
          ↓
Auth.js creates a session and sets a cookie
          ↓
User is logged in, browser stores session cookie

Reading the Session in Server Components

Use the auth() function to read the current session on the server. It returns null if the user is not logged in.

// app/dashboard/page.js
import { auth } from '@/auth';
import { redirect } from 'next/navigation';

export default async function DashboardPage() {
  const session = await auth();

  if (!session) {
    redirect('/login');
  }

  return (
    <main>
      <h1>Welcome, {session.user.name}</h1>
      <p>Email: {session.user.email}</p>
    </main>
  );
}

Protecting Routes with Middleware

Use Auth.js with Next.js middleware to protect entire sections of your site. Unauthenticated users get redirected to the login page before any page code runs.

// middleware.js
import { auth } from '@/auth';

export default auth((request) => {
  const isLoggedIn = !!request.auth;
  const isProtected = request.nextUrl.pathname.startsWith('/dashboard');

  if (isProtected && !isLoggedIn) {
    return Response.redirect(new URL('/login', request.url));
  }
});

export const config = {
  matcher: ['/dashboard/:path*', '/profile/:path*'],
};

Sign In and Sign Out Buttons

// app/components/AuthButtons.js
import { signIn, signOut } from '@/auth';

export function SignInButton() {
  return (
    <form
      action={async () => {
        'use server';
        await signIn('github');
      }}
    >
      <button type="submit">Sign in with GitHub</button>
    </form>
  );
}

export function SignOutButton() {
  return (
    <form
      action={async () => {
        'use server';
        await signOut();
      }}
    >
      <button type="submit">Sign out</button>
    </form>
  );
}

Email and Password Authentication

For username and password login without OAuth, use the Credentials provider. Validate credentials yourself, then return the user object if the login succeeds.

import Credentials from 'next-auth/providers/credentials';
import { verifyPassword } from '@/lib/password';
import { db } from '@/lib/db';

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Credentials({
      async authorize(credentials) {
        const user = await db.users.findOne({ email: credentials.email });

        if (!user) return null;

        const passwordValid = await verifyPassword(
          credentials.password,
          user.hashedPassword
        );

        if (!passwordValid) return null;

        return { id: user.id, name: user.name, email: user.email };
      },
    }),
  ],
});

Session Data Shape

session = {
  user: {
    name: "Alice Johnson",
    email: "alice@example.com",
    image: "https://avatars.githubusercontent.com/u/123"
  },
  expires: "2025-12-31T00:00:00.000Z"
}

Access in components:
session.user.name   → "Alice Johnson"
session.user.email  → "alice@example.com"
session.user.image  → URL of profile photo

Diagram: Where Authentication Happens

LAYER             TOOL                  PURPOSE
──────────────────────────────────────────────────────
Middleware        auth() from Auth.js   Block unauthenticated routes fast
Server Component  auth()                Read session, redirect if needed
Server Action     auth()                Confirm user before DB mutation
Client Component  useSession()          Show/hide UI based on login state
API Route         auth()                Protect API endpoints

Key Takeaway

Use Auth.js to handle authentication in Next.js. Configure OAuth providers like Google and GitHub with just a client ID and secret. Read the session with auth() in server components and server actions. Protect entire route segments using middleware so unauthenticated users never reach protected pages. For email and password login, use the Credentials provider and validate users yourself.

Leave a Comment