How to Access User Sessions in Next.js App Router Middleware (Beginner’s Guide)

How to Access User Sessions in Next.js App Router Middleware

If you are building a modern web application, authentication is usually your top priority. With the introduction of the Next.js App Router, the way we handle routing, data fetching, and security has changed significantly. One of the most powerful tools in this new ecosystem is Middleware.

But how do you securely check if a user is logged in before they even reach a page? In this beginner-friendly guide, we will explore exactly how to access Next.js App Router middleware user sessions, step by step. We will also look at how this fits into a full-stack environment using Node.js, Express, and Apollo Server.

How to Access User Sessions in Next.js

Understanding Next.js App Router Middleware

Before diving into the code, let us understand what middleware is. In Next.js, middleware is a piece of code that runs before a request is completed.

Imagine it like a security guard at the front door of a building. When a user tries to visit a private dashboard (/dashboard), the middleware intercepts the request, checks the user’s ID badge (their session token), and decides whether to let them in or redirect them to the login page.

Managing Next.js App Router middleware user sessions is incredibly fast because Next.js middleware runs on the Edge Runtime. This is a lightweight, super-fast environment. However, because it is lightweight, you cannot use heavy Node.js libraries (like bcrypt or standard database drivers) inside it. You must rely on lightweight, edge-compatible logic.

Why Access Sessions in Middleware?

Handling user sessions inside middleware offers several massive benefits:

  1. Security: Unauthenticated users are blocked from protected routes before the page even begins to render.
  2. Performance: Redirects happen at the edge, closer to the user, making your app feel significantly faster.
  3. Clean Code: You don’t have to write authentication checks inside every single React component.

Prerequisites and Tech Stack Setup

To fully grasp how Next.js App Router middleware user sessions work in the real world, we need a realistic setup. In modern web development, your frontend and backend are often separate.

For this guide, we will assume you are using:

  • Frontend: Next.js (App Router)
  • Backend: Node.js with Express and Apollo Server (GraphQL)
  • Authentication Method: JWT (JSON Web Tokens) stored in HTTP-only cookies.

Note: HTTP-only cookies are the most secure way to store session tokens because they cannot be accessed by malicious JavaScript.

A Quick Look at the Backend (Node.js, Express, Apollo Server)

While the focus of this article is Next.js, let’s quickly see how your Express and Apollo Server backend should set the session cookie when a user logs in.

JavaScript

// backend/server.js (Node.js + Express + Apollo Server)
import express from 'express';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import cookieParser from 'cookie-parser';
import jwt from 'jsonwebtoken';

const app = express();
app.use(cookieParser());
app.use(express.json());

// A simple login resolver
const resolvers = {
  Mutation: {
    login: async (_, { email, password }, context) => {
      // 1. Verify user in database (skipped for brevity)
      const user = { id: 1, email: "user@example.com", role: "admin" };
      
      // 2. Create a session token
      const token = jwt.sign(user, process.env.JWT_SECRET, { expiresIn: '1h' });

      // 3. Set the token in an HTTP-only cookie
      context.res.cookie('session_token', token, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'strict',
        maxAge: 3600000 // 1 hour
      });

      return "Login Successful!";
    },
  },
};

// Setup Apollo Server...
Code language: JavaScript (javascript)

Once the user logs in, their browser stores the session_token cookie. Every time they try to visit a Next.js page, the browser automatically sends this cookie.

Step-by-Step Implementation in Next.js

Now that our backend is sending a session cookie, let’s implement the logic to access Next.js App Router middleware user sessions.

Step 1: Install Edge-Compatible JWT Library

Because Next.js middleware runs on the Edge runtime, standard Node.js libraries like jsonwebtoken will crash. We need to install jose, a library specifically designed to work in edge environments.

Run the following command in your Next.js project:

Bash

npm install jose

Step 2: Creating the Middleware File

In the App Router, your middleware must be placed at the root of your project (or inside the src folder if you are using one), right next to your app directory. The file must be named middleware.ts (or .js).

TypeScript

// src/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { jwtVerify } from 'jose';

// Define the secret used to sign your JWT
// Make sure this matches the secret on your Express/Apollo backend!
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET);

export async function middleware(request: NextRequest) {
    // We will add our session logic here
}
Code language: JavaScript (javascript)

Step 3: Extracting the Session Token

Inside the middleware function, we need to extract the cookie that our backend created. Next.js provides a helpful request.cookies API for this.

TypeScript

export async function middleware(request: NextRequest) {
    // 1. Get the session token from the cookies
    const token = request.cookies.get('session_token')?.value;

    // 2. Define the paths that require authentication
    const isProtectedRoute = request.nextUrl.pathname.startsWith('/dashboard');

    // 3. If there is no token and the user is trying to access a protected route,
    // redirect them to the login page.
    if (!token && isProtectedRoute) {
        return NextResponse.redirect(new URL('/login', request.url));
    }

    // Continue to Step 4...
}
Code language: JavaScript (javascript)

Step 4: Validating the Session

If the token exists, we cannot just blindly trust it. We must verify its cryptographic signature to ensure the user’s session is authentic. Here is how we complete our middleware:

TypeScript

export async function middleware(request: NextRequest) {
    const token = request.cookies.get('session_token')?.value;
    const isProtectedRoute = request.nextUrl.pathname.startsWith('/dashboard');

    if (isProtectedRoute) {
        if (!token) {
            // No session found
            return NextResponse.redirect(new URL('/login', request.url));
        }

        try {
            // Verify the JWT token using the 'jose' library
            const { payload } = await jwtVerify(token, SECRET_KEY);
            
            // Optional: Check user roles inside the payload
            if (payload.role !== 'admin' && request.nextUrl.pathname.startsWith('/dashboard/admin')) {
                return NextResponse.redirect(new URL('/unauthorized', request.url));
            }

            // Session is valid, allow the request to proceed
            return NextResponse.next();

        } catch (error) {
            // Token is invalid or expired
            console.error("Session verification failed:", error);
            
            // Delete the invalid cookie and redirect to login
            const response = NextResponse.redirect(new URL('/login', request.url));
            response.cookies.delete('session_token');
            return response;
        }
    }

    // For public routes, just let the request through
    return NextResponse.next();
}
Code language: JavaScript (javascript)

Step 5: Configuring the Matcher

To ensure your middleware runs efficiently, you should tell Next.js exactly which routes it needs to intercept. You do this by exporting a config object at the bottom of middleware.ts.

TypeScript

export const config = {
    // The matcher strictly defines which routes trigger the middleware
    matcher: [
        /*
         * Match all request paths except for the ones starting with:
         * - api (API routes)
         * - _next/static (static files)
         * - _next/image (image optimization files)
         * - favicon.ico (favicon file)
         */
        '/((?!api|_next/static|_next/image|favicon.ico).*)',
    ],
};
Code language: JavaScript (javascript)

Best Practices for Next.js App Router Middleware User Sessions

To keep your application secure and fast, follow these best practices:

  1. Keep Middleware Lightweight: Middleware runs on every matched request. Do not perform heavy computations or slow database fetches inside it. Rely on fast, stateless JWT verification.
  2. Always Use HTTP-Only Cookies: Never store your session tokens in localStorage. Storing them in HTTP-only cookies prevents Cross-Site Scripting (XSS) attacks from stealing user sessions.
  3. Sync Secrets: Ensure the JWT_SECRET used in your Node.js/Apollo Server backend exactly matches the one used in your Next.js middleware, or validation will fail.
  4. Use the Matcher Wisely: Do not run middleware on static assets (images, CSS, JS bundles). This will unnecessarily slow down your application.

Common Mistakes to Avoid

When developers first learn how to handle Next.js App Router middleware user sessions, they frequently make a few critical errors:

1. Using Node.js Native Modules

As mentioned earlier, importing jsonwebtoken, bcrypt, or fs inside middleware.ts will immediately break your application. The edge runtime does not support them. Always use edge-compatible libraries like jose.

2. Forgetting to Return a NextResponse

If you intercept a request but fail to return NextResponse.next() or NextResponse.redirect(), the user’s browser will hang indefinitely. Every logical path in your middleware must return a response.

3. Trusting the Client Too Much

Just because a middleware lets a user view a page does not mean your backend is secure. If the frontend makes a request to your Apollo Server, the backend must verify the session token again. Middleware secures the UI; your backend secures the data.

Conclusion

Managing Next.js App Router middleware user sessions might seem complicated at first, but it is one of the most robust ways to secure your application. By combining the speed of Next.js edge middleware with a solid backend setup using Node.js, Express, and Apollo Server, you can create a seamless and secure user experience.

Remember the golden rules: use HTTP-only cookies, rely on edge-compatible libraries like jose for validation, and keep your middleware logic lean and fast.

Implement these strategies in your next project, and you will have a rock-solid authentication flow ready for production!

Did you find this guide helpful? Let us know in the comments below, and don’t forget to check out our other tutorials on modern web development!

Leave a Comment

Your email address will not be published. Required fields are marked *