ReactHustle

Password Protect Static Website in NextJS

Jasser Mark Arioste

Jasser Mark Arioste

 Password Protect Static Website in NextJS

Hello, hustlers! In this tutorial, you'll learn how to add password protection to a static website. It will be useful if you have a website under development or if you have preview deployments hosted on Vercel.

How to Add Password Protection #

Here's how we can implement password protection on a NextJS website.

  1. Define an environment variable PASSWORD_PROTECT. We'll use this to check if the user submitted the correct password
  2. Use NextJS middleware to check if the PASSWORD_PROTECT environment variable is present. If it's present, we check a cookie if the user has already logged in before. If not, we redirect the user to the  /password-protection page.
  3. Create a /password-protection page. Once the user submits the form. we'll set the cookie.

We'll cover these steps in detail in the following sections.

Project Setup #

I created a GitHub repo to kickstart our tutorial.  If you have an existing project, you can skip this step. If you're starting from scratch, I recommend using this repo since it already includes TailwindCSS for styling.

To create a NextJS project using the GitHub template, just run this command:

npx create-next-app -e https://github.com/jmarioste/nextjs-password-protect-tutorial nextjs-example
1

To start the local server, run this command:

cd nextjs-example && yarn dev
1

Go to localhost:3000 and you should see the following:

NextJS Password Protect Home Page

Step 1 -  Define PASSWORD_PROTECT Variable #

Next, create a .env file at the root directory of your project and define the PASSWORD_PROTECT environment variable:

PASSWORD_PROTECT="password"
1

We'll use this variable to check if there's a password protection set for a certain environment or deployment. 

Step 2 - Creating Password Protect Page #

Next, let's create the /password-protect page.  We'll use daisyUI to add some styling. Create a file pages/password-protect.tsx:

import React from "react";
import Image from "next/image";
const PasswordProtectPage = () => {
  return (
    <div className="container">
      <div className="grid place-content-center min-h-screen">
        <div className="flex flex-col items-center gap-4">
          <h1 className="text-2xl">This Page is Under Development... </h1>
          <Image
            src="/under-development.svg"
            alt="under development"
            width={250}
            height={250}
          />
          <p>Enter Password:</p>
          <form action="/api/password-protect" method="post">
            <div className="form-control">
              <div className="input-group">
                <input
                  type="text"
                  name="password"
                  className="input input-bordered"
                />
                <button className="btn">Login</button>
              </div>
            </div>
          </form>
        </div>
      </div>
    </div>
  );
};
export default PasswordProtectPage;
123456789101112131415161718192021222324252627282930313233

Explanation:

Line 17: This is a very simple form that submits a POST request to the /api/password-protect endpoint which we'll implement in the next step.

If you go to the /password-protect route right now, you should have something like this:

Next JS Password Protect Page

Step 3 - Implementing the API endpoint #

Next, we'll implement the api/password-protect endpoint to check if the password entered is correct. First, let's install the cookie package to make it easier to write cookies:

yarn add cookie && yarn add -D @types/cookie

Next, create the file pages/api/password-protect.ts:

// api/password-protect.ts
import { NextApiRequest, NextApiResponse } from "next";
import { serialize } from 'cookie';
export default async function handler(req: NextApiRequest, res:NextApiResponse){
    if(req.method !== "POST"){
        res.status(405).send("Method Not Allowed")
    }
    const password = req.body.password;
    if(process.env.PASSWORD_PROTECT === password){
        const cookie = serialize('login', 'true', {
            path: '/',
            httpOnly: true
        })
        res.setHeader('Set-Cookie', cookie)
        res.redirect(302, '/')
    } else {
        const url = new URL("/password-protect", req.headers["origin"])
        url.searchParams.append("error", "Incorrect Password")
        res.redirect(url.toString())
    }
}
123456789101112131415161718192021

Explanation:

Line 8: We limit this endpoint to only accepting POST requests.

Line 9: We get the password from the request body. The body.password field here corresponds to name="password" in our form from earlier.  

Line 10: Check if the inputted password is the same as our environment variable.

Line 11-15: If the password is correct, we set the login cookie indicating that the user has already logged in and won't need the password-protect page in the future. Next, we redirect the user to the index page.

Later, we'll use the login cookie in the middleware function to redirect the user to the password-protect page.

Line 17-19: If the password is incorrect, we redirect back to the password-protect page and pass a query parameter error.

To display the error, modify the password-protect page and check the router.query params:

const PasswordProtectPage = () => {
  const router = useRouter();
  const error = router.query.error;
  ...
  return (
    ...
    <form action="/api/password-protect" method="post">
      <div className="form-control">
        {error && (
          <label className="label">
            <span className="label-text text-error">{error}</span>
          </label>
        )}
         ...
      </div>
    </form>
   ...
    
  )
}
1234567891011121314151617181920

Here's a little demo after this step:

Password Protect Demo

 Our password-protect page mechanism is working, it redirects to the home page and sets the login cookie if the correct password is inputted. 

Step 4 - Implementing the Middleware #

The last step that we need to do is to redirect every route to password-protect page if the login cookie is not set. 

Create a middleware.ts file in your root project directory and add the following code:

import { NextRequest, NextResponse } from "next/server";
const isPasswordEnabled = !!process.env.PASSWORD_PROTECT
export async function middleware(req: NextRequest){
    const isLoggedIn = req.cookies.has('login');
    const isPathPasswordProtect = req.nextUrl.pathname.startsWith("/password-protect")
    if(isPasswordEnabled && !isLoggedIn && !isPathPasswordProtect){
        return NextResponse.redirect(new URL("/password-protect", req.url))
    }
    return NextResponse.next()
}
export const config = {
    matcher: [
      /*
       * Match all request paths except for the ones starting with:
       * - api (API routes)
       * - _next/static (static files)
       * - favicon.ico (favicon file)
       */
      '/((?!api|_next/static|favicon.ico|under-development.svg).*)',
    ],
  }
123456789101112131415161718192021

Explanation:

We'll redirect all pages to the password-protect page if it matches the following criteria:

  1. If the environment is password protected. We check this via the PASSWORD_PROTECT environment variable
  2. If the user is not logged in. We check this via the login cookie
  3. If the pathname is not password-protect page. We have to check this since it will trigger an infinite redirect if we don't.

That's It! Your website is now password protected, it doesn't matter if it's a static page or dynamic page since we're doing the check inside the middleware.

The full code is available at GitHub. The demo is available at Vercel.

Conclusion #

We learned how to add password protection to protect a website in NextJS. We used a combination of NextJS Middleware and cookies to implement the logic. We used DaisyUI + TailwindCSS to create an awesome password-protect page component.

If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on Twitter.

Further Reading #

Improve your NextJS knowledge by reading our previous tutorials:

Share this post!

Related Posts

Disclaimer

This content may contain links to products, software and services. Please assume all such links are affiliate links which may result in my earning commissions and fees.
As an Amazon Associate, I earn from qualifying purchases. This means that whenever you buy a product on Amazon from a link on our site, we receive a small percentage of its price at no extra cost to you. This helps us continue to provide valuable content and reviews to you. Thank you for your support!
Donate to ReactHustle