Background image

Simple password protection for a Next.js app

blog main image

WIP The fastest way to implement password protection for your Next.js app, just need to know few WEB APIs and single file logic:

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.

Password Protection Components

To implement simple password protection in a Next.js app, we need following ingredients ๐Ÿงช:

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:
// excludes static assets
export const config = { matcher: ['/((?!_next).*)'] } 

export default function middleware() { ... }
  • A basic auth check function, which decodes the base64 header and compares it to predefined credentials:
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
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
javascript
typescript