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

easy-node-captcha

v2.0.2

Published

A simple, secure Node.js CAPTCHA library with encrypted token validation. Supports text and math CAPTCHAs with customizable options.

Readme

Easy Node Captcha 🔐

A simple, secure Node.js CAPTCHA library with encrypted token validation. Supports both text and math CAPTCHAs with customizable options.

Features ✨

  • 🔒 Secure Token Encryption - Uses AES-256-CBC encryption, no eval()
  • ⏱️ Token Expiration - Configurable validity period
  • 🧮 Math CAPTCHAs - Optional math equation challenges with complexity levels
  • 🎨 Customizable - Control size, noise, colors, and characters
  • 🎯 Custom Fonts - Avoid bundler issues with custom font support
  • 🖼️ Data URI Support - Perfect for React/JSX, no dangerouslySetInnerHTML
  • 📝 Full TypeScript Support - Complete type definitions
  • 🌳 Tree-Shakeable - Optimized for modern bundlers
  • 🚀 Easy Integration - Simple API with minimal setup

Installation

npm install easy-node-captcha

Quick Start

const captcha = require("easy-node-captcha");

// 1. Configure your secret key (do this once when server starts)
captcha.configure("your-super-secret-key-here");

// 2. Generate a CAPTCHA
const { image, token } = captcha.generate();

// 3. Verify user input
const result = captcha.verify(token, userInput);
if (result.valid) {
  console.log(result.message); // 'Captcha verified successfully.'
}

API Reference

configure(secretOrOptions)

Configure the secret key for encryption. Must be called before using generate or verify.

// Simple configuration
captcha.configure("my-secret-key-minimum-32-chars-recommended");

// Advanced configuration with custom font
captcha.configure({
  secret: "my-secret-key",
  defaultFontPath: "/absolute/path/to/custom-font.ttf"
});

Parameters:

  • secretOrOptions (string | object) - Secret string or configuration object
    • secret (string) - A long, random string for encryption
    • defaultFontPath (string, optional) - Default font file path
    • defaultFontBuffer (Buffer, optional) - Default font as Buffer

generate(options)

Generates a new CAPTCHA image and encrypted token.

const { image, token } = captcha.generate({
  size: 6,
  ignoreChars: "0o1ilI",
  noise: 2,
  background: "#f0f0f0"
});

Parameters:

  • options (object, optional)
    • size (number) - Number of characters (default: 4)
    • ignoreChars (string) - Characters to exclude
    • noise (number) - Number of noise lines (default: 1)
    • background (string) - Background color
    • math (boolean) - Generate math equation instead of text
    • mathMin (number) - Minimum value for math (default: 1)
    • mathMax (number) - Maximum value for math (default: 9)
    • mathOperator (string) - Operators: '+', '-', or '+-' (default: '+')
    • mathComplexity (string) - Complexity: 'easy', 'medium', or 'hard'
    • fontPath (string) - Custom font file path (solves bundler issues)
    • fontBuffer (Buffer) - Custom font as Buffer
    • caseSensitive (boolean) - If true, verification is case-sensitive (default: false)
    • dataUri (boolean) - If true, includes base64 data URI (default: false)

Returns:

  • image (string) - SVG string to display in HTML
  • token (string) - Encrypted token to store (hidden input)
  • dataUri (string, optional) - Base64 data URI (only if dataUri option is true)

verify(token, input, validityInMinutes)

Verifies user input against the encrypted token.

const result = captcha.verify(token, userInput, 5);

Parameters:

  • token (string) - The encrypted token from generate()
  • input (string) - User's CAPTCHA input
  • validityInMinutes (number, optional) - Token expiration time (default: 10)

Returns:

  • object - Verification result
    • valid (boolean) - True if valid and not expired, false otherwise
    • message (string) - Descriptive message about the result

Usage Examples

Basic Text CAPTCHA

const express = require("express");
const captcha = require("easy-node-captcha");

const app = express();
app.use(express.urlencoded({ extended: true }));

// Configure secret key
captcha.configure("my-super-secret-key-change-this-in-production");

// Generate CAPTCHA route
app.get("/captcha", (req, res) => {
  const { image, token } = captcha.generate();

  res.send(`
        <form method="POST" action="/verify">
            ${image}
            <input type="hidden" name="captchaToken" value="${token}">
            <input type="text" name="captchaInput" placeholder="Enter CAPTCHA">
            <button type="submit">Verify</button>
        </form>
    `);
});

// Verify CAPTCHA route
app.post("/verify", (req, res) => {
  const { captchaToken, captchaInput } = req.body;
  const result = captcha.verify(captchaToken, captchaInput, 10);

  res.send(result.valid ? `✅ ${result.message}` : `❌ ${result.message}`);
});

app.listen(3000);

Math CAPTCHA with Complexity Levels

// Easy: Simple operations (1 + 2)
const easy = captcha.generate({
  math: true,
  mathComplexity: "easy",
  mathMin: 1,
  mathMax: 10
});

// Medium: Two operations (5 * 4 - 2)
const medium = captcha.generate({
  math: true,
  mathComplexity: "medium",
  mathMin: 1,
  mathMax: 20
});

// Hard: Complex with parentheses ((12 / 2) + 5)
const hard = captcha.generate({
  math: true,
  mathComplexity: "hard",
  mathMin: 1,
  mathMax: 50,
  mathOperator: "+-"
});

Custom Fonts (Solves Bundler Issues)

// Using custom font file path
const { image, token } = captcha.generate({
  fontPath: "/absolute/path/to/custom-font.ttf"
});

// Or configure globally
captcha.configure({
  secret: "my-secret-key",
  defaultFontPath: "/path/to/font.ttf"
});

// Using font buffer (for embedded fonts)
const fs = require("fs");
const fontBuffer = fs.readFileSync("./fonts/custom.ttf");

const { image, token } = captcha.generate({
  fontBuffer: fontBuffer
});

Custom Styling

const { image, token } = captcha.generate({
  size: 6,
  ignoreChars: "0o1ilI",
  noise: 3,
  background: "#cc9966",
  color: true,
  width: 200,
  height: 80
});

Short-lived CAPTCHA

// Token expires in 2 minutes
const result = captcha.verify(token, userInput, 2);
if (!result.valid) {
  console.log(result.message); // 'Token expired. Please request a new captcha.'
}

Case-Sensitive Verification

// Generate case-sensitive CAPTCHA
const { image, token } = captcha.generate({
  caseSensitive: true,
  size: 6
});

// Now 'ABC' and 'abc' will be treated as different
const result1 = captcha.verify(token, "ABC"); // valid if CAPTCHA was 'ABC'
const result2 = captcha.verify(token, "abc"); // invalid if CAPTCHA was 'ABC'

// Default behavior (case-insensitive)
const { image, token } = captcha.generate({
  caseSensitive: false // or omit this option
});
// 'ABC' and 'abc' will be treated as the same

Data URI for React/JSX (Safer Alternative)

// Generate with data URI
const { dataUri, token } = captcha.generate({
  dataUri: true,
  size: 6
});

// React component (no dangerouslySetInnerHTML needed!)
function CaptchaForm() {
  return (
    <form>
      <img src={dataUri} alt="CAPTCHA" />
      <input type="hidden" name="token" value={token} />
      <input type="text" name="answer" placeholder="Enter CAPTCHA" />
      <button type="submit">Verify</button>
    </form>
  );
}

// Express example with data URI
app.get("/captcha", (req, res) => {
  const { dataUri, token } = captcha.generate({ dataUri: true });

  res.send(`
    <form method="POST" action="/verify">
      <img src="${dataUri}" alt="CAPTCHA" />
      <input type="hidden" name="token" value="${token}">
      <input type="text" name="answer" placeholder="Enter CAPTCHA">
      <button type="submit">Verify</button>
    </form>
  `);
});

TypeScript Usage

import * as captcha from "easy-node-captcha";
import type { CaptchaOptions, VerifyResult } from "easy-node-captcha/types";

// Configure
captcha.configure({
  secret: process.env.CAPTCHA_SECRET!,
  defaultFontPath: "./fonts/custom.ttf"
});

// Generate with type safety
const options: CaptchaOptions = {
  size: 6,
  mathComplexity: "medium",
  noise: 2
};

const { image, token } = captcha.generate(options);

// Verify with typed result
const result: VerifyResult = captcha.verify(token, userInput, 10);
if (result.valid) {
  console.log("Success:", result.message);
}

Security Best Practices 🔐

  1. Use a strong secret key: At least 32 random characters
  2. Store the secret securely: Use environment variables
  3. Set appropriate expiration: 5-10 minutes is recommended
  4. Regenerate after failed attempts: Prevent brute force attacks
  5. Never expose the token: Keep it in a hidden input field
// Good practice
captcha.configure(process.env.CAPTCHA_SECRET_KEY);

How It Works

  1. Generation: Creates an SVG CAPTCHA image and encrypts the answer with a timestamp
  2. Storage: The encrypted token is sent to the client (hidden input)
  3. Verification: Decrypts the token, checks expiration, and compares the answer
  4. Security: AES-256-CBC encryption with timestamp-based expiration

Framework-Specific Notes

Next.js Users

If you encounter a "font not found" error when using this package in Next.js, add the following configuration to your next.config.js (or next.config.ts):

JavaScript:

module.exports = {
  serverExternalPackages: ["svg-captcha", "easy-node-captcha"]
};

TypeScript:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  serverExternalPackages: ["svg-captcha", "easy-node-captcha"]
};

export default nextConfig;

This tells Next.js to exclude these packages from bundling, resolving font loading issues in server-side rendering.

Alternative: Use the fontPath or fontBuffer option to provide a custom font, which completely eliminates dependency on svg-captcha's default fonts.

captcha.generate({
  fontPath: "/absolute/path/to/your/font.ttf"
});

What's New in v2.0.0 🎉

🔒 Security Improvements (v2.0.1)

  • Removed eval() from math complexity calculations
  • Uses safe calculator function instead
  • Passes security scanners (SonarQube, Snyk, etc.)
  • No functional changes, just safer code

🖼️ Data URI Support (v2.0.1)

  • New dataUri option returns base64 data URI
  • Perfect for React/JSX: Use <img src={captcha.dataUri} />
  • No more dangerouslySetInnerHTML needed!
  • Cleaner and safer frontend code

Enhanced Verification Response

  • verify() now returns an object { valid: boolean, message: string } instead of just a boolean
  • Get descriptive error messages: "Token expired", "Incorrect captcha", "Invalid token", etc.
  • Better user feedback and debugging

Case Sensitivity Control

  • New caseSensitive option in generate()
  • Default: case-insensitive ('ABC' === 'abc')
  • Set caseSensitive: true for strict matching
  • Useful for security-critical applications

Custom Font Support

  • Add fontPath or fontBuffer to generate() options
  • Configure default fonts globally in configure()
  • Solves bundler issues with Next.js, Webpack, Vercel
  • No more ENOENT font file errors

Math Complexity Levels

  • mathComplexity: 'easy' - Simple operations (1 + 2)
  • mathComplexity: 'medium' - Two operations (5 * 4 - 2)
  • mathComplexity: 'hard' - Complex with parentheses ((12 / 2) + 5)
  • Makes math CAPTCHAs more flexible for different use cases

Better TypeScript Support

  • Separate types file for tree-shaking
  • Import types without triggering side effects: import type { ... } from 'easy-node-captcha/types'
  • Full type definitions for all options and return values
  • Works flawlessly with modern bundlers

Package Exports

  • Proper exports field in package.json
  • Better tree-shaking and bundle optimization
  • Cleaner imports for both CommonJS and ES modules

Requirements

  • Node.js >= 12.0.0
  • No external dependencies except svg-captcha

License

MIT License - See LICENSE file for details

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Issues

Found a bug or have a feature request? Open an issue

Author

Rohit Kumar Yadav - GitHub


Made with ❤️ for the Node.js community