Engineering

⚙️ Building a Cryptographically Secure Password API in Node.js and Python

By Ateeq Y Tanoli, · 4 May 2026 · 3 min read · 477 words

Building a Cryptographically Secure Password API in Node.js and Python

A well-designed password generation API is a critical building block for IAM systems, developer tools, and security platforms. This guide shows you how to build one in Node.js and Python that uses CSPRNG and follows industry best practices. For managing passwords generated by your API, Bitwarden provides an open-source vault that integrates with any workflow.

API Design Principles

A password generation API should be:

  1. Cryptographically random — always use CSPRNG, never Math.random()
  2. Configurable — length, character sets, exclusion rules
  3. Rate-limited — prevent abuse and brute-force enumeration
  4. Auditable — log generation events without storing plaintext passwords
  5. Verifiable — clients can validate generated passwords against policy

Node.js Implementation (Express)

const express = require('express');
const crypto = require('crypto');
const rateLimit = require('express-rate-limit');

const app = express();

const CHARSETS = {
    lowercase: 'abcdefghijklmnopqrstuvwxyz',
    uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
    digits: '0123456789',
    special: '!@#$%^&*()_+-=[]{}|;:,.<>?'
};

function generatePassword({ length = 16, useLowercase = true, useUppercase = true, useDigits = true, useSpecial = false, exclude = '' }) {
    let pool = '';
    if (useLowercase) pool += CHARSETS.lowercase;
    if (useUppercase) pool += CHARSETS.uppercase;
    if (useDigits) pool += CHARSETS.digits;
    if (useSpecial) pool += CHARSETS.special;

    // Remove excluded characters
    for (const ch of exclude) {
        pool = pool.replace(new RegExp(`[${ch}]`, 'g'), '');
    }

    if (pool.length === 0) throw new Error('No characters available');

    const bytes = crypto.randomBytes(length);
    let password = '';
    for (let i = 0; i < length; i++) {
        password += pool[bytes[i] % pool.length];
    }
    return password;
}

const limiter = rateLimit({
    windowMs: 60 * 1000, // 1 minute
    max: 60,             // 60 requests per minute
    message: { error: 'Rate limit exceeded' }
});

app.get('/api/generate', limiter, (req, res) => {
    try {
        const password = generatePassword({
            length: Math.min(parseInt(req.query.length) || 16, 128),
            useLowercase: req.query.lowercase !== 'false',
            useUppercase: req.query.uppercase !== 'false',
            useDigits: req.query.digits !== 'false',
            useSpecial: req.query.special === 'true',
            exclude: req.query.exclude || ''
        });
        res.json({ password, length: password.length });
    } catch (err) {
        res.status(400).json({ error: err.message });
    }
});

app.listen(3000);

Python Implementation (FastAPI)

from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
import secrets
import string

app = FastAPI(title="Secure Password API")

CHARSETS = {
    "lowercase": string.ascii_lowercase,
    "uppercase": string.ascii_uppercase,
    "digits": string.digits,
    "special": "!@#$%^&*()_+-=[]{}|;:,.<>?"
}

@app.get("/api/generate")
async def generate(
    length: int = Query(16, ge=4, le=128),
    lowercase: bool = True,
    uppercase: bool = True,
    digits: bool = True,
    special: bool = False,
    exclude: str = ""
):
    pool = ""
    if lowercase: pool += CHARSETS["lowercase"]
    if uppercase: pool += CHARSETS["uppercase"]
    if digits: pool += CHARSETS["digits"]
    if special: pool += CHARSETS["special"]

    for ch in exclude:
        pool = pool.replace(ch, "")

    if not pool:
        raise HTTPException(400, "No characters available")

    password = "".join(secrets.choice(pool) for _ in range(length))
    return {"password": password, "length": len(password)}

Security Considerations

  1. HTTPS only — never serve a password API over plain HTTP
  2. Authentication — require API keys for production deployments
  3. Audit logging — log request timestamps and IPs, never log passwords
  4. Output validation — verify generated passwords meet policy before returning
  5. Error messages — do not reveal implementation details in error responses
Generate a Free Strong Password →

More Password Security Tools

🔑 SecureKeyGen⚔️ TitanPasswords🛡️ Best Password Generator🔐 Free Strong Password⚡ Instant Password🗝️ Iron Vault Keys👨‍👩‍👧‍👦 Safe Pass Builder🛡️ Trusty Password⚙️ StrongPassFactory🔑 SecureKeyGen.org📚 TrustyPassword.org

Building a Cryptographically Secure Password API in Node.js

A password API is the gatekeeper of every user account, so its security cannot be an afterthought. In Node.js, building one correctly means leaning on well-vetted cryptographic primitives rather than rolling your own. The goal is to ensure that even if your database is breached, the stored credentials remain computationally infeasible to reverse. This requires careful choices around hashing algorithms, randomness, and timing-safe comparisons.

Choosing the Right Hashing Algorithm

Never store passwords in plaintext or with fast hashes like MD5 or SHA-256 alone. These are designed for speed, which is exactly what attackers exploit when running billions of guesses per second. Instead, use a deliberately slow, memory-hard algorithm. Node.js ships with a native crypto.scrypt implementation, and the ecosystem offers excellent libraries for Argon2 and bcrypt, the current industry favorites.

Generating Secure Random Salts

Each password must be hashed with a unique, cryptographically random salt to defeat rainbow tables and prevent identical passwords from producing identical hashes. Use crypto.randomBytes rather than Math.random, which is not cryptographically secure. A salt of at least 16 bytes is recommended. Many modern libraries like Argon2 generate and embed the salt automatically within the output string, simplifying storage and retrieval.

Designing the API Endpoints

A minimal password API exposes registration and verification routes. On registration, accept the plaintext password over a TLS-encrypted connection, hash it server-side, and persist only the resulting digest. On login, retrieve the stored hash and verify the submitted password against it. Critically, never log raw passwords and never return hash values in API responses.

Preventing Timing Attacks

When comparing values, avoid the standard equality operator, which short-circuits and leaks timing information. Use crypto.timingSafeEqual for constant-time comparisons. Reputable hashing libraries handle this internally during verification, but any custom token or API-key comparison in your code should adopt the same discipline to close subtle side channels.

Operational Best Practices

Security is ongoing, not a one-time setup. Tune your hashing cost parameters periodically so they remain expensive as hardware improves, and re-hash passwords transparently on the user's next successful login. Enforce rate limiting on authentication endpoints to slow brute-force attempts, and add a server-side pepper stored separately from the database for an extra defense layer.

By combining strong algorithms, secure randomness, and disciplined comparisons, your Node.js password API becomes a resilient foundation for user trust.

We use cookies to improve your experience. Learn more

Store passwords with NordPass.