Recently I had the opportunity to play around with the Next.js Middleware (Beta). Instead of going for the out-of-the-box samples I took upon the challenge to validate the authorization of a user before sending them the staticly generated page content.
Client side authorization?
In a Jamstack world authorization too often ends-up on client side, this unfortunately happens without much thinking. In a developer experience (DX) that pushes all router logic over onto the front-end side of things it becomes something natural to validate the logged in state of a user and redirect the user to the log-in page if validation fails. Now before you become completely baffled by this, in many cases this actually isn't too much of a problem.
Let me explain, in most Jamstack web apps all fetching of personal information or actions executed by a user end up as a call to a back-end service. This could be a specific SaaS product, a custom firebase app, a tailor made serverless function or something else completely. But in all cases these back-end services should and will validate the logged in state of a user and immediately return an 403 Unauthorized if something is wrong. It's then back to the front-end to handle this properly and redirect the user toward the log-in page.
However in some cases, your headless CMS could also contain protected data. As that data is static on build time it will probably end up as a static HTML page. To avoid this you could disable the static build of this page, and fetch all data dynamically at run time. But where does that leave your choice for a Jamstack web app? Performance is decreased for sure, and your user experience will be impacted as protected pages will take a lot longer to load.
Middelware to the rescue
A piece of middleware could actually solve this by only processing the logged in state validation on a request of a user and return the static HTML if all is well or redirect to the log-in page. With Next.js version 12, middleware has been added as a new beta feature. Depending on your hosting this is already supported partially or in full if you are using the direct hosting options from Vercel.
To add a piece of middleware to a Next.js application you can simply create a _middleware.js
file in any pages subdirectory to only run the middleware for a subset of pages, or directly in the pages folder to apply to all pages. In our example, when adding an authorization validation it would be more likely to work within an authenticated pages subpath (eg: /extra).
Inside the _middleware.js
file the following code is then placed. The middelware function gets the authentication token from the "auth" cookie. Sends a post request to the Back-End to validate the authorization of the user. Depending on the response from the Back-End the user receives the actual protected page or is redirected to the sign-in page.
import { NextResponse } from "next/server";
const VALIDATE_AUTHORIZATION_API = "https://.../auth";
const getCookieValue = function (cookieString, cookiename) {
var cookiestring = RegExp(cookiename + "=[^;]+").exec(cookieString);
return decodeURIComponent(
!!cookiestring ? cookiestring.toString().replace(/^[^=]+./, "") : ""
);
};
export async function middleware(req) {
if (req.nextUrl && req.nextUrl.href) {
// Get Authentication Token from auth cookie
var cookieValue = getCookieValue(req.headers.get("cookie"), "auth");
const auth = cookieValue ? JSON.parse(cookieValue) : undefined;
// Validate if the Authentication Token is valid with a Back-End call
const authResponse = await fetch(VALIDATE_AUTHORIZATION_API, {
method: "POST",
headers: {
Authorization: `Bearer ${auth.token}`,
},
})
.then((response) => response.status)
.catch(() => 401);
// If the server indicates that the user isn't authoarizd, redirect to login
if (authResponse === 401) {
return Response.redirect("/sign-in", 401);
}
}
// If no url passed or user is authorized continue the normal flow
const res = NextResponse.next();
return res;
}
Things to keep in mind when using Next.js middleware
- Everything that happens in the middleware function will be exectued before every page request and as such add extra loading time, increasing the TTFB (Time To First Byte) metric. (web.dev TTFB)
- Middleware is executed on the Edge, meaning that execution duration is limited. On Vercel, the function needs to return a response in less than 1.5 seconds. Vercel Documentation On Netlify a bit more time is available with 10 seconds before the reuqest will time out. Netlify Documentation
- Native Node.js APIs are not supported. For example, you can't read or write to the filesystem
- Calling
require
directly is not allowed. Use ES Modules instead - Environment variables aren't avaiable