Protect Nextra Pages
05 Oct 2023ยท4 min readยท๐ฌ๐งBackground
Nextra is Next.js template for putting documentation in MDX files. I used Nextra for this website (docs.fyfirman.com) which contains private or confidential things. Since this my personal notes which may I want to share with others, I still want to make them live on the internet but can protect the public from accessing them. Hence, I'm looking for a "Password Protection" feature for this website.
Proposed Solution
NGINX basic auth
On the company where I working right now, there's a website which is protected by username & password. This is very minimal & simple which fits my case. However, my website is deployed in Vercel and I don't want to change it because it's very simple to deploy & change it. I don't want to add complexity to the deployment process.
Vercel Password Protection
A place where I deploy is providing the solution. It's called "Deployment Protection" it might be perfect, but my wallet can't afford it ๐. They took $150/month.
Cloudflare Worker
I used Cloudflare to put my DNS. I loved Cloudflare. I found this article which uses a worker to add password protection.
After I tried that solution, adding a route to my subdomain is not working. I am still able to access docs.fyfirman.com
without submitting a password. I assume the DNS will work first, then redirect the request to Vercel without running the worker.
Vercel Basic Auth Template
Vercel has edge or equal Cloudflare worker in their environment. They also provide a template for it, however, it's basic Next.js.
We will use the edge as middleware, so every request that matches it will run the function to check whether the auth is provided or not. The first thing we need to do is add middleware.ts
at the root of projects, which contains these code:
import { NextRequest, NextResponse } from "next/server";
export const config = {
matcher: ["/private/(.*)"],
};
export function middleware(req: NextRequest) {
const basicAuth = req.headers.get("authorization");
const url = req.nextUrl;
if (basicAuth) {
const authValue = basicAuth.split(" ")[1];
const [user, pwd] = atob(authValue).split(":");
if (user === "4dmin" && pwd === "testpwd123") {
return NextResponse.next();
}
}
url.pathname = "/api/auth";
return NextResponse.rewrite(url);
}
That code will redirect to the endpoint /api/auth
if the authentication is not provided. The API just showing 401 because the authorization will happen on the above file. Then, for the API we can create at /pages/api/auth.ts
import type { NextApiRequest, NextApiResponse } from "next";
export default function handler(_: NextApiRequest, res: NextApiResponse) {
res.setHeader("WWW-authenticate", 'Basic realm="Secure Area"');
res.statusCode = 401;
res.end(`Auth Required.`);
}
Finally, we got the result:
Continue with Basic Auth Middleware
Fixing Auth from Other Routes
In the previous code, we can protect a route if the user directly puts the address to the browser. But, if the user already visited another route and visited a private route, the user is able to see the pages even though they are not filling username and password.
To fix that, we can implement the middleware to chunk URLs by modifying the config matcher in middleware.ts
:
export const config = {
matcher: [
'/private/(.*)',
'/_next/static/chunks/pages/private/(.*)'
],
}
...
Resource Usage
Since I used Vercel Hobby which is free, it has a resource limitation per month. The middleware is counted as Edge Middleware Invocations. It's a limit of 1,000,000 which is very far from normal usage which only me.
Vercel Limitation