Next.js Password Protection

First published: October 16, 2025 (1y ago)

how toweb developmentscripting

Protecting early work, prototypes, and in-progress features with password protection is a smart practice for teams and individuals. Not only does this prevent sensitive ideas, intellectual property, and unfinished designs from being viewed publicly, it also reduces the attack surface for would-be malicious actors. Early-stage projects often lack the hardened security and polish of production apps, so limiting access ensures creations and innovations remain safe until they're ready to be shared with the world.

My preferred framework and service for hosting prototypes are Next.js and Vercel. While Vercel offers built-in password protection for deployments, this feature is only available as an add-on for USD$150/month! A steep cost for many solo developers, students, and small teams. While looking for a simpler and cheaper alternative, I came up with the following approach using HTTP Basic Auth via Next.js middleware. This approach provides a robust gate for staging environments or previews, helping keep early work private and secure without incurring additional expenses.

What is Next.js middleware?

You can define Next.js middleware by creating a special file called middleware.ts (or .js) in your project's root or relevant directory.

It will then be executed automatically for every matching request defined in its matcher config and allows you to run code before a request is completed. This code can modify the request, response, and response headers.

It doesn’t return UI, but we can run code to change the request/response pipeline to check if the user has supplied the correct username and password, and if not, inject our own headers to make the browser prompt for them.

HTTP Basic auth using middleware.ts

// middleware.ts

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(req: NextRequest) {
	// Get the username and password from the environment
	const envUser = process.env?.USERNAME || '';
	const envPassword = process.env?.PASSWORD || '';

	// If username or password env vars are not set, skip auth
	if (envUser === '' || envPassword === '') {
		return NextResponse.next();
	}

	// Get the auth header from the request
	const basicAuth = req.headers.get('authorization');

	if (basicAuth) {
		// Decode the username and password from the auth header
		const authValue = basicAuth.split(' ')[1];
		const [user, password] = Buffer.from(authValue, 'base64')
			.toString()
			.split(':');

		// If the values match, complete the request!
		if (user === envUser && password === envPassword) {
			return NextResponse.next();
		}
	}

	// Deny the request and tell the browser to show the HTTP Basic Auth prompt
	return new NextResponse('Auth required', {
		status: 401,
		headers: { 'WWW-authenticate': 'Basic realm="Secure Area"' },
	});
}

// Run on every request
// (Update this if you only want to secure certain parts of the site)
export const config = {
	matcher: '/:path*',
};

Pros / Cons

Pros

  • No additional subscription fee for passwords
  • No additional infrastructure or SaaS integrations
  • Can set the username and password on a per-environment basis using env vars
  • No additional dependencies
  • Minimal code required
  • Easy to disable/remove (just delete the env vars or file)

Cons

  • Requires a server running / doesn't work with static export
  • Middleware runs on every request
  • The UI & Ux for HTTP Basic Auth is not great (subjective opinion)

Alternatives

The nextjs-basic-auth-middleware library performs a similar task. However I prefer to minimise external dependencies in situations like this where the solution can be implemented in a few lines of code.