How to Implement JWT Auth in Next.js Middleware

Introduction

Next.js middleware runs on the edge, making it perfect for authentication checks before pages render. The jose library provides JWT verification that works in edge runtimes.

Step 1: Install jose

npm install jose

Step 2: Create Middleware

import { jwtVerify } from "jose";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

const secret = new TextEncoder().encode(process.env.JWT_SECRET);

export async function middleware(request: NextRequest) {
  const token = request.cookies.get("token")?.value;

  if (!token) {
    return NextResponse.redirect(new URL("/login", request.url));
  }

  try {
    const { payload } = await jwtVerify(token, secret);
    const response = NextResponse.next();
    response.headers.set("x-user-id", String(payload.sub));
    return response;
  } catch {
    return NextResponse.redirect(new URL("/login", request.url));
  }
}

export const config = {
  matcher: ["/dashboard/:path*", "/api/private/:path*"],
};

Step 3: Set Cookies on Login

import { SignJWT } from "jose";

async function login(email: string, password: string) {
  const user = await authenticate(email, password);
  const token = await new SignJWT({ sub: user.id })
    .setProtectedHeader({ alg: "HS256" })
    .setExpirationTime("1h")
    .sign(secret);

  const response = NextResponse.json({ user });
  response.cookies.set("token", token, {
    httpOnly: true,
    secure: true,
    sameSite: "strict",
  });
  return response;
}

Conclusion

Next.js middleware with jose provides edge-compatible JWT authentication that runs before any page or API route. The matcher config ensures only protected routes are checked, keeping public routes fast.