npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

express-simple-pass

v3.0.0

Published

Simple password gate middleware for Express. Protect admin UIs and internal tools without building a full authentication system.

Readme

express-simple-pass

An Express middleware for protecting admin UIs and dashboards with a simple password gate. Perfect for securing monitoring tools, admin panels, and internal dashboards without implementing a full authentication system.

Requirements

  • Express 5 or above
  • Node.js 18 or above

Installation

npm install express-simple-pass
# or
yarn add express-simple-pass
# or
pnpm add express-simple-pass

ESM Only

This package is ESM only

Basic Example

import express from "express";
import { SimplePass } from "express-simple-pass";
import Agendash from "agendash";
import agenda from "./agenda-instance";
import argon2 from "argon2";

const app = express();

const simplepass = new SimplePass({
  type: "passkey",
  verify: (passkey) => argon2.verify(process.env.PASS_KEY_HASH, passkey),
  cookie: {
    secret: process.env.COOKIE_SECRET,
    secure: process.env.NODE_ENV === "production"
  }
});

app.use(express.urlencoded({ extended: true }));
app.use(simplepass.router());

app.use("/admin/agendash", simplepass.guard, Agendash(agenda));

app.listen(3000);

Authentication Types

Passkey

A single shared secret.

const simplepass = new SimplePass({
  type: "passkey",
  verify: (passkey) => argon2.verify(process.env.PASS_KEY_HASH, passkey),
  cookie: {
    secret: process.env.COOKIE_SECRET,
    secure: process.env.NODE_ENV === "production"
  }
});

Email and Password

const simplepass = new SimplePass({
  type: "email-password",
  verify: async (email, password) => {
    const admin = await db.users.findOne({ email, role: "admin" });

    return admin ? await argon2.verify(admin.password_hash, password) : false;
  },
  cookie: {
    secret: process.env.COOKIE_SECRET,
    secure: process.env.NODE_ENV === "production"
  }
});

Protecting Routes

Use simplepass.guard as middleware on any route or group of routes. Unauthenticated requests are redirected to the authentication page and returned to their original destination after login.

app.use("/admin", simplepass.guard, admin_router);

Checking Authentication Programmatically

const authenticated = await simplepass.passed(req);

Returns true if the request carries a valid, non-expired token.

API

new SimplePass(options)

| Option | Type | Required | Description | | ---------- | ------------------------------- | -------- | --------------------------------------------------- | | type | "passkey" \| "email-password" | Yes | Authentication type | | verify | VerifyFn | Yes | Credential verification function | | cookie | SimplePassCookieOptions | Yes | Cookie configuration | | rootpath | `/${string}` | No | Root path for auth routes. Default: "/simplepass" | | theming | SimplePassTheming | No | Theming and customization options |

SimplePassCookieOptions

| Option | Type | Required | Description | | -------- | ---------------------------------- | -------- | ------------------------------------------------------ | | secret | string \| Record<number, string> | Yes | Encryption secret, minimum 32 characters | | secure | boolean | Yes | Whether to set the Secure flag on the cookie | | ttl | number | No | Token lifetime in seconds. Default: 43200 (12 hours) | | name | string | No | Cookie name. Default: "simplepass" |

All standard cookie SerializeOptions are also accepted (e.g. domain, sameSite, path).

simplepass.router()

Returns an Express router that serves the authentication page and handles form submissions. Must be mounted on your app before any protected routes.

simplepass.guard

Express middleware that protects a route. Redirects unauthenticated requests to the authentication page, preserving the original URL for post-login redirect.

simplepass.passed(req)

Async method that returns true if the request is authenticated.

The verify Function

The verify function receives the submitted credentials and must return true if they are valid. It also receives a context object containing the raw Express request, which can be used for rate limiting, IP checks, or logging.

Passkey

verify: (passkey: string, context: { req: Request }) =>
  boolean | Promise<boolean>;

Email and Password

verify: (email: string, password: string, context: { req: Request }) =>
  boolean | Promise<boolean>;

Cookie Security

The secure option

The secure option controls whether the Secure flag is set on the authentication cookie. There is no default — you must set it explicitly.

Set secure: true in any environment served over HTTPS. Set secure: false only in local development.

cookie: {
  secret: process.env.COOKIE_SECRET,
  secure: process.env.NODE_ENV === "production",
}

Note: if your app runs behind a reverse proxy (nginx, Caddy, AWS ALB, etc.) and you rely on req.secure, make sure to set app.set("trust proxy", true) in Express. See the Express behind proxies guide for details.

The secret option

The cookie secret is used to encrypt and authenticate the session token via iron-webcrypto. It must be at least 32 characters long.

Generate a secure secret:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Password Rotation

To rotate your secret without invalidating all active sessions, pass a map of numbered passwords. The highest numeric key is used to seal new tokens. All passwords are tried during verification.

cookie: {
  secret: {
    1: process.env.OLD_COOKIE_SECRET,
    2: process.env.NEW_COOKIE_SECRET,
  },
  secure: true
}

Once enough time has passed for old tokens to have expired, remove the old password from the map.


Customization

const simplepass = new SimplePass({
  type: "passkey",
  verify: (passkey) => argon2.verify(process.env.PASS_KEY_HASH, passkey),
  cookie: {
    secret: process.env.COOKIE_SECRET,
    secure: true
  },
  theming: {
    font: {
      url: "https://fonts.googleapis.com/css2?family=Inter&display=swap",
      family: "Inter, sans-serif"
    },
    css: "/absolute/path/to/your/styles.css",
    labels: {
      title: "Admin Access",
      instruction: "Enter the passkey to continue",
      submit: "Continue",
      logout: "Sign out",
      logged_out: "You have been signed out."
    }
  }
});

Labels

Customize all static text on the authentication page via theming.labels:

theming: {
  labels: {
    title: "Admin Access",
    instruction: "Enter your credentials to continue",
    email_placeholder: "Email",
    password_placeholder: "Password",
    passkey_placeholder: "Pass key",
    submit: "Continue",
    logout: "Sign out",
    logged_out: "You have been signed out.",
    unauthorized: "Not authorized.",
    already_authenticated: "You are already authenticated.",
  }
}

| Label | Description | Default (passkey) | Default (email-password) | | ----------------------- | ----------------------------------------------------------- | ---------------------------------- | -------------------------------------- | | title | Page heading | "Authentication" | "Authentication" | | instruction | Subheading below title | "Enter the pass key to continue" | "Enter your credentials to continue" | | passkey_placeholder | Passkey input placeholder | "Pass key" | — | | email_placeholder | Email input placeholder | — | "Email" | | password_placeholder | Password input placeholder | — | "Password" | | submit | Submit button text | "Submit" | "Submit" | | logout | Log out button text | "Log out" | "Log out" | | logged_out | Message shown after logging out | "You have been logged out." | "You have been logged out." | | unauthorized | Error shown when logging out without being authenticated | "Not authorized." | "Not authorized." | | already_authenticated | Error shown when authenticating while already authenticated | "You are already authenticated." | "You are already authenticated." |

Font

Load and apply a custom font via theming.font:

theming: {
  font: {
    url: "https://fonts.googleapis.com/css2?family=Inter&display=swap",
    family: "Inter, sans-serif",
  }
}

| Property | Description | | -------- | ------------------------------------------------------------------------- | | url | A Google Fonts or any stylesheet URL to load the font from | | family | The font-family value to apply, e.g. "Inter" or "Inter, sans-serif" |

Styling

The authentication page is dark themed by default. There are two levels of customization available: CSS variables for quick theming, and direct class or element targeting for full control.

Providing custom CSS

Pass either an absolute path to a .css file or a raw CSS string via theming.css.

theming: {
  css: "/absolute/path/to/your/styles.css",
}

Or inline:

theming: {
  css: `
    :root {
      --sp-bg: #ffffff;
      --sp-surface: #f9f9f9;
      --sp-text: #09090b;
    }
  `,
}

CSS variables

The quickest way to retheme the page is to override the --sp-* CSS custom properties. Overriding variables at :root cascades through every rule automatically.

:root {
  /* Backgrounds */
  --sp-bg: #09090b; /* Page background */
  --sp-surface: #111113; /* Card background */

  /* Borders */
  --sp-border: #27272a; /* Default border color */
  --sp-border-focus: #52525b; /* Input border on focus */

  /* Text */
  --sp-text: #fafafa; /* Primary text */
  --sp-text-muted: #a1a1aa; /* Instruction text, log out button */
  --sp-text-placeholder: #52525b; /* Input placeholder text */

  /* Button */
  --sp-btn-bg: #fafafa; /* Submit button background */
  --sp-btn-bg-hover: #e4e4e7; /* Submit button hover */
  --sp-btn-bg-active: #d4d4d8; /* Submit button active/pressed */
  --sp-btn-text: #09090b; /* Submit button text */

  /* Alerts */
  --sp-error: #f87171; /* Error text */
  --sp-error-bg: #1c0a0a; /* Error background */
  --sp-error-border: #7f1d1d; /* Error border */
  --sp-success: #34d399; /* Success text */
  --sp-success-bg: #022c22; /* Success background */
  --sp-success-border: #064e3b; /* Success border */

  /* Misc */
  --sp-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --sp-radius: 0.5rem; /* Border radius applied throughout */
  --sp-shadow: 0 0 0 1px #27272a; /* Card shadow */
  --sp-input-height: 2.5rem; /* Height of inputs and submit button */
  --sp-gap: 20px; /* Spacing between sections */
}

Example: light theme

:root {
  --sp-bg: #ffffff;
  --sp-surface: #fafafa;
  --sp-border: #e4e4e7;
  --sp-border-focus: #a1a1aa;
  --sp-text: #09090b;
  --sp-text-muted: #71717a;
  --sp-text-placeholder: #a1a1aa;
  --sp-btn-bg: #18181b;
  --sp-btn-bg-hover: #27272a;
  --sp-btn-bg-active: #09090b;
  --sp-btn-text: #fafafa;
  --sp-error: #dc2626;
  --sp-error-bg: #fef2f2;
  --sp-error-border: #fecaca;
  --sp-success: #16a34a;
  --sp-success-bg: #f0fdf4;
  --sp-success-border: #bbf7d0;
  --sp-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.07);
}

CSS classes and IDs

Every element on the page has a stable class or ID you can target directly.

Layout

| Selector | Element | | --------------- | --------------------------------------------------------------------- | | body | Page body — controls background and centering | | .container | The card wrapping the entire form | | .header | Wrapper around title and instruction | | .fields | Wrapper around all inputs | | .fields-group | Wrapper around email and password inputs (email-password type only) | | .actions | Wrapper around the submit button | | .logout-form | The log out form |

Elements

| Selector | Element | | --------------- | ------------------------------ | | .title | The page heading | | .instruction | The subheading below the title | | .input | All input fields | | #email | Email input specifically | | #password | Password input specifically | | #passkey | Passkey input specifically | | form button | Submit button | | button.logout | Log out button |

Alerts

| Selector | Element | | ---------------- | ---------------------------------------- | | .alert | Alert container (both error and success) | | .alert.error | Error alert | | .alert.success | Success alert |