⚙️ Building a Cryptographically Secure Password API in Node.js and Python
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:
- Cryptographically random — always use CSPRNG, never Math.random()
- Configurable — length, character sets, exclusion rules
- Rate-limited — prevent abuse and brute-force enumeration
- Auditable — log generation events without storing plaintext passwords
- 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
- HTTPS only — never serve a password API over plain HTTP
- Authentication — require API keys for production deployments
- Audit logging — log request timestamps and IPs, never log passwords
- Output validation — verify generated passwords meet policy before returning
- Error messages — do not reveal implementation details in error responses