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.
Maintainers
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-captchaQuick 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 objectsecret(string) - A long, random string for encryptiondefaultFontPath(string, optional) - Default font file pathdefaultFontBuffer(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 excludenoise(number) - Number of noise lines (default: 1)background(string) - Background colormath(boolean) - Generate math equation instead of textmathMin(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 BuffercaseSensitive(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 HTMLtoken(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 inputvalidityInMinutes(number, optional) - Token expiration time (default: 10)
Returns:
object- Verification resultvalid(boolean) - True if valid and not expired, false otherwisemessage(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 sameData 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 🔐
- Use a strong secret key: At least 32 random characters
- Store the secret securely: Use environment variables
- Set appropriate expiration: 5-10 minutes is recommended
- Regenerate after failed attempts: Prevent brute force attacks
- Never expose the token: Keep it in a hidden input field
// Good practice
captcha.configure(process.env.CAPTCHA_SECRET_KEY);How It Works
- Generation: Creates an SVG CAPTCHA image and encrypts the answer with a timestamp
- Storage: The encrypted token is sent to the client (hidden input)
- Verification: Decrypts the token, checks expiration, and compares the answer
- 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
dataUrioption returns base64 data URI - Perfect for React/JSX: Use
<img src={captcha.dataUri} /> - No more
dangerouslySetInnerHTMLneeded! - 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
caseSensitiveoption in generate() - Default: case-insensitive ('ABC' === 'abc')
- Set
caseSensitive: truefor strict matching - Useful for security-critical applications
Custom Font Support
- Add
fontPathorfontBufferto generate() options - Configure default fonts globally in configure()
- Solves bundler issues with Next.js, Webpack, Vercel
- No more
ENOENTfont 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
exportsfield 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
