Patrick Desjardins Blog
Patrick Desjardins picture from a conference

NextJS 14 Authentication and Middleware

Posted on: 2024-04-14

NextJS 14 Authentication and Middleware Limitation

Middleware has changed since NextJS 13. A single middleware file exists at the root, restricting access to the request and response objects. Enforcing authentication for web and API routes fits well in the middleware as they are a required path for each request. The advantage is that authentication becomes transparent to every developer who uses the system. However, the middleware does not allow for the request to be modified. The lack of mutability means that the middleware cannot add user information to the request object. The paucity of modification is a problem because, ideally, the web and API routes receive a user object from the middleware.

NextJS Solution

The solution is to use HTTP headers. There are some limitations in terms of size, but overall, you can pass serialized information in the request header.

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
  // ...
  const user = getUserFromToken(request.headers.get('Authorization'));
  // ...
  const headers = new Headers(request.headers);
  headers.set('user', JSON.stringify(user));
  // ...
  return NextResponse.next({
    headers,
  })
}

The solution could be better because it requires the user to be deserialized in every route. The friction with NextJS continues to grow as the app route does not expose the request object. Without a request object, the developer must deserialize the header in two ways: one for the web route and one for the API route.

The app route for the web page can access the header and retrieve the user.

export default async function DefaultPage() {
  const user = JSON.parse(headers().get('user'));
  // ...
}

The api route can access the request:

export async function GET(request: NextRequest) {
  const user = JSON.parse(request.headers.get('user'));
  // ...
}

For the API route, there is potential for altering the request object. NextJS only blocks the mutation between the middleware and the route. The API route can call a function at the beginning of the route to deserialize the user and add it to the request object. The explicit call to a function is a potential solution to the problem of managing the header differently and making the web route cleaner, as the information remains in the request object for the life of the request.

function addUserToRequest(request: NextRequest){
  const user = JSON.parse(request.headers.get('user'));
  request.user = user;
}

export async function GET(request: NextRequest) {
  addUserToRequest(request); // Call this function at each API GET/POST/PUT/DELETE route
  // Request has the user 
  console.log(request.user);
}

The solution could be better, but it is a workaround to the limitation of the middleware that NextJS imposes. Ideally, the request object would have the value set by the middleware.