Menu

13. Authentication & Authorization

Next.js Master Roadmap - IT Technology

This chapter explains how to implement authentication using server‑side sessions, secure cookies, and JSON Web Tokens, and how to integrate Auth.js (NextAuth) for social logins and credentials‑based authentication. It also covers authorization strategies—roles, permissions, and protected routes—along with security best practices.

No MCQ questions available for this chapter.

13. Authentication & Authorization

Server‑Side Session Storage

In a typical Next.js application, user state is kept on the server. A random, cryptographically‑strong sessionId is generated upon login and sent to the client inside an HttpOnly cookie. On every subsequent request the middleware reads the cookie, looks up the session in a SessionStore (e.g., a database or Redis), and attaches the associated user object to req.

Example middleware (Express‑style pseudo‑code):

if (req.cookies.sessionId) {
    const session = await SessionStore.find(req.cookies.sessionId);
    req.user = session.user;
  }

The cookie should be configured with security‑focused attributes:

  • HttpOnly – prevents client‑side JavaScript access, mitigating XSS theft.
  • Secure – ensures the cookie is transmitted only over HTTPS.
  • SameSiteStrict or Lax reduces CSRF risk by limiting cross‑site sending.
  • Path=/ – makes the cookie available site‑wide.
  • Max‑Age – lifetime in seconds (e.g., 3600 for one hour).

An example cookie string:

session=abc123def456; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400

Cookies: Key‑Value Pairs and Flags

Cookies are automatically attached to HTTP requests by the browser. Their syntax is a series of name=value pairs separated by semicolons, followed by optional attributes.

AttributePurpose
HttpOnlyBlocks access via document.cookie (XSS protection).
SecureSent only over TLS‑encrypted connections.
SameSiteValues: Strict, Lax, None (with Secure). Controls cross‑site request inclusion.
PathURL path that must match for the cookie to be sent.
Max-Age / ExpiresDefines lifetime; Max-Age=3600 means one hour.

JSON Web Tokens (JWT)

A JWT is a compact, URL‑safe token consisting of three Base64Url‑encoded parts separated by dots:

header.payload.signature

Header

Contains the token type and signing algorithm.

{"alg":"HS256","typ":"JWT"}

Payload (Claims)

Holds user‑identifying information and metadata.

{"sub":"1234567890","name":"Alice","iat":1717000000,"role":"admin"}

Signature

Created by hashing the encoded header and payload with a secret (or private key). For HS256:

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

Verification recomputes the signature and compares it to the supplied one; if they match, the payload is trusted.

Example token (HS256 with secret mysecret):

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNzE3MDAwMDAwLCJyb2xlIjoiYWRtaW4ifQ.HMACSHA256Signature

Auth.js (NextAuth) Overview

NextAuth.js (now @auth/core + next-auth) simplifies authentication in Next.js by providing providers, session management, and JWT handling out of the box.

Setup

  1. Install the packages:
  2. npm install next-auth @auth/core
  3. Create an API route that exports authOptions and the NextAuth handler.

Example using the app router (app/api/auth/[...nextauth]/route.ts):

import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";
import CredentialsProvider from "next-auth/providers/credentials";
import { compare } from "bcrypt";

export const authOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_ID!,
      clientSecret: process.env.GOOGLE_SECRET!,
    }),
    CredentialsProvider({
      name: "Credentials",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" },
      },
      authorize: async (credentials) => {
        const user = await db.user.findUnique({
          where: { email: credentials?.email },
        });
        if (user && await compare(credentials?.password ?? "", user.password)) {
          return { id: user.id, email: user.email, role: user.role };
        }
        return null;
      },
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET,
  callbacks: {
    jwt: ({ token, user }) => {
      if (user) token.role = user.role;
      return token;
    },
    session: ({ session, token }) => {
      if (session.user) session.user.role = token.role as string;
      return session;
    },
  },
};

export default NextAuth(authOptions);

Google Login

When the user clicks “Sign in with Google”, NextAuth redirects them to Google’s OAuth 2.0 consent screen. After approval, Google redirects back with an authorization code. NextAuth exchanges this code for an access token and ID token, extracts the user’s profile (name, email, picture), and either stores a JWT in a cookie or creates a server‑side session, depending on the configuration.

GitHub Login

Similar to Google; typical scopes are read:user and user:email. The returned data includes the GitHub username, avatar URL, and primary email address.

Credentials Login

Allows a custom email/password flow. The developer supplies an authorize callback that validates credentials against a data store (often using bcrypt for password hashing). On success, NextAuth returns a user object that is serialized into a session or JWT.

Authorization: Roles, Permissions, and Protected Routes

Roles

Roles are coarse‑grained access levels stored as a string or enum (e.g., "admin", "moderator", "user"). They are frequently included as a claim in the JWT or session object (role: "admin").

Permissions

Permissions are fine‑grained actions such as "create:post", "delete:comment", "read:own_profile". They can be derived from a role‑permission matrix or stored as an array in the user record (permissions: ["create:post","edit:own_post"]).

Protected Routes with NextAuth Middleware

NextAuth provides a withAuth higher‑order function that can be used in middleware.ts to protect pages or API routes.

import { withAuth } from "next-auth/middleware";

export default withAuth(
  function (req) {
    // Optional custom logic
  },
  {
    callbacks: {
      authorized: ({ token }) => !!token && token.role