nextjs-password-protect
v1.0.4
Published
A reusable password protection module for Next.js applications with secure token-based authentication
Downloads
270
Maintainers
Readme
NextJs Password Protect Package
A reusable password protection module for Next.js applications that provides a beautiful, customizable password protection screen with secure token-based authentication.
Installation
npm install nextjs-password-protect
# or
yarn add nextjs-password-protect
# or
pnpm add nextjs-password-protectFeatures
- 🔒 Simple password-based authentication
- 🎨 Modern, responsive UI that automatically inherits your application's theme
- 🌓 Automatic theme support (light/dark mode) via CSS variables
- 🖼️ Custom brand logo support
- 💾 Optional localStorage persistence
- 🔐 Secure token-based authentication (prevents localStorage manipulation)
- ⚙️ Highly configurable
- 📦 Reusable package structure
- 🎯 TypeScript support
Quick Start
Step 1: Install the Package
npm install nextjs-password-protectStep 2: Create API Route
Copy the API route template to your Next.js app:
Option A: Copy from node_modules
cp node_modules/nextjs-password-protect/api-route-template.ts app/api/auth/verify/route.tsOption B: Create manually
Create app/api/auth/verify/route.ts:
import { NextRequest, NextResponse } from "next/server";
import { createHash, randomBytes } from "crypto";
// In-memory token store (in production, consider using Redis or a database)
// Format: { tokenHash: { expiresAt: number, createdAt: number } }
const tokenStore = new Map<string, { expiresAt: number; createdAt: number }>();
// Clean up expired tokens periodically
setInterval(() => {
const now = Date.now();
for (const [tokenHash, data] of tokenStore.entries()) {
if (data.expiresAt < now) {
tokenStore.delete(tokenHash);
}
}
}, 60000); // Clean up every minute
function generateToken(): string {
return randomBytes(32).toString("hex");
}
function createTokenHash(token: string): string {
return createHash("sha256").update(token).digest("hex");
}
export async function POST(request: NextRequest) {
try {
const { password, token: requestToken } = await request.json();
// Token validation endpoint (prevents localStorage manipulation)
if (requestToken) {
const tokenHash = createTokenHash(requestToken);
const tokenData = tokenStore.get(tokenHash);
if (!tokenData) {
return NextResponse.json(
{ success: false, error: "Invalid token" },
{ status: 401 }
);
}
if (tokenData.expiresAt < Date.now()) {
tokenStore.delete(tokenHash);
return NextResponse.json(
{ success: false, error: "Token expired" },
{ status: 401 }
);
}
// Token is valid, refresh expiration
const expiresIn = 24 * 60 * 60 * 1000; // 24 hours
tokenData.expiresAt = Date.now() + expiresIn;
return NextResponse.json({ success: true, valid: true });
}
// Password validation endpoint
if (!password) {
return NextResponse.json(
{ success: false, error: "Password is required" },
{ status: 400 }
);
}
// Get password from server-side environment variable (NOT NEXT_PUBLIC)
const correctPassword = process.env.APP_PASSWORD || "demo123";
if (password !== correctPassword) {
return NextResponse.json(
{ success: false, error: "Incorrect password" },
{ status: 401 }
);
}
// Generate secure token
const newToken = generateToken();
const tokenHash = createTokenHash(newToken);
const expiresIn = 24 * 60 * 60 * 1000; // 24 hours
tokenStore.set(tokenHash, {
expiresAt: Date.now() + expiresIn,
createdAt: Date.now(),
});
return NextResponse.json({
success: true,
token: newToken, // Return plain token to client (hash is stored server-side)
});
} catch (error) {
return NextResponse.json(
{ success: false, error: "Invalid request" },
{ status: 400 }
);
}
}Step 3: Configure Environment Variable
Create .env.local in your project root:
APP_PASSWORD=your-secure-password-hereStep 4: Add Tailwind4 CSS Config
Create tailwind.config.ts:
import type { Config } from "tailwindcss";
const Config = {
content: [
"./node_modules/nextjs-password-protect/**/*.{js,ts,jsx,tsx}", // Include the Password Protect files
],
darkMode: "class",
};
export default Config;Step 5: Include Tailwind4 CSS in app/globals.css Top of the CSS file
@config "./../tailwind.config.ts";Step 6: Use in Your App
Wrap your application content in app/layout.tsx:
import { PasswordProtectWrapper } from "nextjs-password-protect";
export default function RootLayout({ children }) {
return (
<html>
<body>
<PasswordProtectWrapper
config={{
logo: "/logo.svg", // Optional
title: "Password Protected", // Optional
}}
>
{children}
</PasswordProtectWrapper>
</body>
</html>
);
}Basic Usage
import { PasswordProtectWrapper } from "nextjs-password-protect";
export default function RootLayout({ children }) {
return (
<html>
<body>
<PasswordProtectWrapper
config={
{
// Password validated via /api/auth/verify endpoint
// Set APP_PASSWORD in .env.local (without NEXT_PUBLIC prefix)
}
}
>
{children}
</PasswordProtectWrapper>
</body>
</html>
);
}API Reference
PasswordProtectWrapper
The main wrapper component that protects your application.
Props:
children: ReactNode - The content to protectconfig: PasswordProtectConfig - Configuration object
PasswordProtectConfig
| Property | Type | Default | Description |
| -------------- | --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| password | string | undefined | ⚠️ Deprecated - Password for client-side validation (visible in bundle). Omit to use secure server-side validation. |
| apiEndpoint | string | "/api/auth/verify" | API endpoint for server-side password validation |
| logo | string \| ReactNode | undefined | Optional brand logo (URL/path or React component) |
| title | string | "Password Protected" | Title text for the password screen |
| description | string | "Please enter the password..." | Description text |
| placeholder | string | "Enter password" | Placeholder for password input |
| errorMessage | string | "Incorrect password..." | Error message on wrong password |
| classNames | object | undefined | Custom class names for UI elements. Keys: wrapper, container, logo, heading, description, input, button, errormessage |
| onSuccess | () => void | undefined | Callback on successful authentication |
| onError | () => void | undefined | Callback on failed authentication |
| storageKey | string | "password-protect-auth" | localStorage key for persistence |
| persistAuth | boolean | true | Whether to persist auth state |
Styling
The component automatically inherits your application's theme using CSS variables:
--background: Background color (defaults to white)--foreground: Text color (defaults to dark gray)--border: Border color (defaults to light gray)
The component will automatically adapt to your application's theme (light/dark mode) without any additional configuration. You can customize the appearance using the classNames prop to target specific UI elements, or by overriding styles.
classNames object keys:
wrapper: Outer wrappercontainer: Main containerlogo: Logo elementheading: Heading textdescription: Description textinput: Input fieldbutton: Submit buttonerrormessage: Error message
Note: If your application uses different CSS variable names, you can override styles using the classNames prop or by providing custom CSS.
Security Notes
⚠️ Important Security Considerations:
Token-Based Authentication: The package uses secure token-based authentication to prevent localStorage manipulation attacks. Tokens are:
- Generated server-side using cryptographically secure random bytes
- Hashed and stored server-side (only hash is stored, not the token)
- Validated on every page load/refresh
- Automatically expire after 24 hours
- Cannot be forged by manually setting localStorage
Server-Side Validation (Recommended): By default, the package uses server-side validation via API route. The password is stored in
APP_PASSWORD(withoutNEXT_PUBLICprefix) and never exposed to the client bundle.Client-Side Protection Limitation: If using the legacy
passwordprop in config, the password will be visible in the JavaScript bundle and localStorage can be manually manipulated. This is only suitable for casual access control, NOT for sensitive data.Token Storage: Tokens are stored in-memory on the server. For production with multiple server instances, consider using Redis or a database for token storage.
Password Storage: Never commit passwords to version control. Always use environment variables in
.env.local(which should be in.gitignore).HTTPS: Always use HTTPS in production to prevent password and token interception during transmission.
Not for Sensitive Data: This is a basic password protection. For production applications with sensitive data, implement proper authentication systems (OAuth, JWT, etc.).
Examples
See EXAMPLE.md for detailed usage examples.
Setup Guide
See SETUP.md for detailed setup instructions.
License
MIT
