Password protect page in Next.js
Whether you’re showcasing your portfolio or your website is handling sensitive information, implementing password protection for your web pages might be required. In this article, we’ll delve into the process of adding password protection to pages generated with Next.js 13.
What We Aim to Achieve
- When a user navigates to a protected page, we will present a modal dialog that prompts them to enter a password.
- After entering the correct password, the dialog will disappear, allowing the content to load, and the password will be securely stored in the user’s cache.
- This cached password will enable users to bypass the password protection on subsequent visits.
- Additionally, we will provide a method for skipping password entry by incorporating a query parameter into the URL, sparing users from manual password input.
Setting Up Environment Variables
Let’s move forward by creating the .env.local file in the root folder of your Next.js project and defining the necessary variables within it. These variables will serve as the keys to ensuring that only authorized users can access your protected content.
PASSWORD_COOKIE_NAME=hasAccess // key for a cookie to check if user is authenticated
SEARCH_QUERY_NAME=password // key for url query to check if url contains password
PAGE_PASSWORD=PasswordForWebsite // actual page password
Creating the Password Prompt Modal
In this section, we’ll design a password prompt modal that incorporates a password field, allowing users to authenticate. Once the user submits the form within this modal, we’ll proceed to send a POST request to a designated API route, a topic we’ll explore in the next step.
// components/PasswordPromptDialog.tsx
"use client";
import React, { useState } from 'react';
const PasswordPromptDialog = ({ onSubmit }) => {
const [password, setPassword] = useState('');
const [passwordIncorrect, setPasswordIncorrect] = useState(false)
const [loading, setLoading] = useState(false);
const handleSubmit = (e) => {
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
const request = await fetch(`/api`, {
body: JSON.stringify({password}),
headers: {"Content-Type": "application/json"},
method: "post",
});
if (request.status !== 200)
return setPasswordIncorrect(true), setLoading(false);
else window.location.reload();
};
return (
<div className=”password-prompt-dialog”>
<form onSubmit={handleSubmit}>
<label htmlFor=”password”>Password:</label>
<input
type=”password”
id=”password”
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type=”submit”>Submit</button>
</form>
</div>
);
};
export default PasswordPromptDialog;
Handling the POST Request
Next, we need to handle the POST request to the API route when the user submits the form. We’ll create a route in a dedicated folder under the Next.js api directory.
Creating the API Route
Under api folder, create a new file named route.ts that handles the POST request:
// api/route.ts
import { serialize } from "cookie";
export async function POST(request: Request, params: { slug: string }) {
const data: { password: string } = await request.json();
const password = data.password;
const cookie = serialize(process.env.PASSWORD_COOKIE_NAME!, “true”,
{
httpOnly: true,
path: “/”,
});
if (process.env.PAGE_PASSWORD !== password) {
return new Response("incorrect password", {
status: 401,
});
}
return new Response("password correct", {
status: 200,
headers: {
"Set-Cookie": cookie,
},
});
}
When the user enters the correct password we send a response and set cookies using an environment variable as a key for the cookie.
For adding cookie data, i utilize the cookie library, which simplifies serialization of the data.
Creating a password protected page
We leverage cookies to determine whether a user is authenticated or not. In cases where authentication is required, we display the previously created <PasswordPromptDialog />. Otherwise, when a user is authenticated, we proceed to load and render the page content.
// app/page.tsx
const Page = (props) => {
const cookiesStore = cookies();
const loginCookies cookiesStore.get(process.env.PASSWORD_COOKIE_NAME!);
const isLoggedIn = !!loginCookies?.value;
if (!isLoggedIn) {
return <PasswordPromptDialog />;
} else {
// User is authenticated, load data and render content
// …
}
}
In this example, we are utilising cookies function of Next.js to read the cookies that were set in <PasswordPromptDialog /> component.
Skipping manual password entry with URL query
Let’s take it one step further and allow users with whom we share our website url to access the protected page without the need for password.
To achieve this, we embed the password directly into the URL as a query parameter, eliminating the need for manual password input.
This automation is made possible through Next.js middleware:
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const password = request.nextUrl.searchParams.get(
process.env.SEARCH_QUERY_NAME!
);
const hasCookie = request.cookies.has(process.env.PASSWORD_COOKIE_NAME!);
const url = request.nextUrl.clone();
const response = NextResponse.redirect(url);
if (password === process.env.PAGE_PASSWORD && !hasCookie) {
response.cookies.set(`${process.env.PASSWORD_COOKIE_NAME}`, "true");
return response;
}
}
When sharing your website with users, simply use your URL with the query key you’ve configured in your environment variables. For example, example.com/protected?password=yourPassword. You can apply this query to any route within your URL, and it will function in the same way.
Hope this tutorial helps you to setup page password protection for your website.
Happy Coding!