Simple password protection for a Next.js app

blog main image

How to set up simple password protection in Next.js - without a database and UI?

  • The easiest way is to enable Vercel password protection, but it requires a Pro plan 💵.
  • Or with a free tier you can do it with Cloudflare, although it involves multiple steps (up to 12).

Or, you can implement it in just a few lines of code: without libraries, no database or third-party services.

Ingredients

To implement simple password protection in a Next.js app, we need following ingredients 🧪:

Copied!
return new Response('Auth Required', {
    status: 401, // status 401 is required, won't work without it
    headers: {'WWW-Authenticate': 'Basic realm="Secure Area"'}
})
  • Next.js Middleware wraps specified routes with executable logic where we validate authentication:
Copied!
export const config = { matcher: ['/((?!_next).*)'] } // excludes static assets

export default function middleware() { ... }
  • A basic auth check function, which decodes the base64 header and compares it to predefined credentials:
Copied!
export const isAuthorized = (authHeader: string | null): boolean => {
    if (!authHeader?.startsWith('Basic ')) {
        return false
    }

    const decoded = Buffer.from(authHeader.slice(6).trim(), 'base64').toString('utf8')
    const [user, pass] = decoded.split(':')
    return user === BASIC_USER && pass === BASIC_PASS // should be stored in env variables
}

Full Code

middleware.ts
Copied!
import {NextRequest, NextResponse} from 'next/server'

const BASIC_USER = process.env.BASIC_USER ?? ''
const BASIC_PASS = process.env.BASIC_PASS ?? ''

if (!BASIC_USER || !BASIC_PASS) {
    throw new Error('Missing env.BASIC_USER or env.BASIC_PASS')
}

export const isAuthorized = (authHeader: string | null): boolean => {
    if (!authHeader || !authHeader?.startsWith('Basic ')) {
        return false
    }

    const decoded = Buffer.from(authHeader.slice(6).trim(), 'base64').toString('utf8')
    const [user, pass] = decoded.split(':')
    return user === BASIC_USER && pass === BASIC_PASS
}

export function middleware(req: NextRequest) {
    const authHeader = req.headers.get('authorization')
    if (isAuthorized(authHeader)) {
        return NextResponse.next()
    }

    return new Response('Auth Required', {
        status: 401,
        headers: {'WWW-Authenticate': 'Basic realm="Secure Area"'}
    })
}

export const config = {
    matcher: '/((?!_next).*)'
}

When you visit a protected page, the browser will display a login prompt:

test
  • Once the user submits valid credentials, the browser sends the appropriate Authorization header on each subsequent request, so that prompt not shown till header is passing auth check 🎉

⚠️

Middleware troubleshoot, in case it does not triggered:

  • Ensure the file is placed in the correct directory:
    • app router: ./middleware.ts in the root of the project or in ./src/middleware.ts in case src structure used.
    • page router: ./pages/_middleware.ts or ./src/pages/_middleware.ts
    • server.js and other cases where middleware is not supported: logic should be also placed in middleware, for example: express middleware

Summary

This approach provides a lightweight password protection mechanism for Next.js apps:

  • No user interface or login page needed.
  • No database or third-party service involved.
  • Easy/quick to implement and sufficient for internal tools or staging environments.
  • Bonus 🌟: those pages won't be indexed/parsed by Google bots/crawlers, which is good option of keeping dev environment private.
⚠️

Keep in mind that this solution is vulnerable to brute-force attacks, and the credentials are not encrypted. It should only be used for staging or temporary environments.



auth
nextjs
web
react