Secure JavaScript password generator
This browser function generates a 20-character password containing lowercase, uppercase, numeric, and symbol characters.
const GROUPS = [
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"0123456789",
"!@#$%^&*()-_=+",
];
const ALPHABET = GROUPS.join("");
function secureIndex(limit) {
if (!Number.isSafeInteger(limit) || limit < 1) {
throw new RangeError("limit must be a positive integer");
}
const range = 0x100000000;
const cutoff = range - (range % limit);
const values = new Uint32Array(1);
do {
globalThis.crypto.getRandomValues(values);
} while (values[0] >= cutoff);
return values[0] % limit;
}
function generatePassword(length = 20) {
if (!Number.isSafeInteger(length) || length < GROUPS.length) {
throw new RangeError(`length must be at least ${GROUPS.length}`);
}
while (true) {
let password = "";
for (let index = 0; index < length; index += 1) {
password += ALPHABET[secureIndex(ALPHABET.length)];
}
const satisfiesPolicy = GROUPS.every((group) =>
[...group].some((character) => password.includes(character))
);
if (satisfiesPolicy) return password;
}
}
console.log(generatePassword(20));
The function works in current browsers. It uses globalThis.crypto.getRandomValues() instead of Math.random(), then rejects out-of-range integers so every character position has the same probability.
Use Web Crypto, not Math.random()
The Web Cryptography specification defines getRandomValues() for cryptographically strong random values. MDN explicitly warns that Math.random() is not cryptographically secure and should not be used for security-sensitive output.
Random-looking output is not enough. Credentials need a cryptographic source that is seeded from operating-system entropy. Do not substitute a timestamp, predictable seed, linear congruential generator, or a package that ultimately delegates to Math.random().
Why the helper uses rejection sampling
A 32-bit value has 4,294,967,296 possible outcomes. That count usually is not evenly divisible by the alphabet length. Applying randomValue % alphabet.length directly would make some characters slightly more likely than others.
cutofffinds the largest multiple of the alphabet length below the 32-bit range.- Values at or above that cutoff are discarded.
- Modulo is applied only after the remaining range divides evenly.
This is the same general unbiased-selection principle used by secure bounded-integer APIs. Rejection is rare for a small password alphabet.
Minimal HTML, CSS, and JavaScript project
Keep the generator function in password-generator.js. Add a small interface that writes the result into an output element. Use textContent, not innerHTML, when displaying generated text.
<label for="length">Password length</label>
<input id="length" type="number" min="4" max="128" value="20">
<button id="generate" type="button">Generate password</button>
<output id="password" aria-live="polite"></output>
<script src="password-generator.js"></script>
<script>
const lengthInput = document.querySelector("#length");
const output = document.querySelector("#password");
document.querySelector("#generate").addEventListener("click", () => {
output.textContent = generatePassword(lengthInput.valueAsNumber);
});
</script>
body {
font: 16px/1.5 system-ui, sans-serif;
max-width: 40rem;
margin: 3rem auto;
padding: 0 1rem;
}
label, input, button, output {
display: block;
margin-block: 0.75rem;
}
output {
overflow-wrap: anywhere;
font-family: ui-monospace, monospace;
}
Reference styles.css from the document head if you use the optional stylesheet. A public GitHub project should include a clear license, a short README, and tests. Do not commit generated production credentials as sample output.
Node.js password generator
For a server-side script, Node's crypto.randomInt() returns a bounded random integer and avoids modulo bias. This version uses ECMAScript modules:
import { randomInt } from "node:crypto";
const GROUPS = [
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"0123456789",
"!@#$%^&*()-_=+",
];
const ALPHABET = GROUPS.join("");
export function generatePassword(length = 20) {
if (!Number.isSafeInteger(length) || length < GROUPS.length) {
throw new RangeError(`length must be at least ${GROUPS.length}`);
}
while (true) {
let password = "";
for (let index = 0; index < length; index += 1) {
password += ALPHABET[randomInt(ALPHABET.length)];
}
if (GROUPS.every((group) =>
[...group].some((character) => password.includes(character)))) {
return password;
}
}
}
console.log(generatePassword(20));
Change the password policy
Edit the strings in GROUPS to match the receiving system's documented rules. If symbols are not accepted, remove the symbol group and increase the password length when possible. If ambiguous characters such as 0, O, 1, l, and I cause transcription errors, remove them before building ALPHABET.
Keep policy and randomness separate. The group check verifies the required character types; secureIndex() controls unbiased selection. Avoid forcing groups into fixed positions because predictable placement reduces the number of possible formats.
Test the generator
Test length, required groups, uniqueness within a practical sample, and invalid input. Do not assert one exact password because secure output is intentionally non-deterministic.
const samples = new Set();
for (let sample = 0; sample < 1_000; sample += 1) {
const value = generatePassword(20);
if (value.length !== 20) throw new Error("wrong length");
if (!GROUPS.every((group) =>
[...group].some((character) => value.includes(character)))) {
throw new Error("password violates the policy");
}
samples.add(value);
}
if (samples.size !== 1_000) throw new Error("duplicate sample");
Common JavaScript password-generator mistakes
- Using
Math.random(): it is not a cryptographic random source. - Using raw modulo:
randomValue % alphabet.lengthintroduces bias unless the ranges divide evenly. - Deleting characters afterward: define the accepted alphabet before selection instead.
- Forcing fixed positions: predictable group placement reduces possible arrangements.
- Injecting with
innerHTML: usetextContentfor generated output. - Logging output: secure randomness cannot protect a password exposed through logs or analytics.
- Storing plaintext passwords: authentication systems need an appropriate salted password-hashing design.
No JavaScript project required
If you need a password rather than source code, use the browser password generator. It already runs locally with Web Crypto and provides length, compatibility, and exclusion controls. For JVM code, use the Java password generator guide. For Python, use the Python password generator guide.
Sources and tested scope
The browser and Node.js functions and their policy checks were executed with Node.js on October 2, 2026. The browser API behavior was reviewed against the current Web Crypto specification and MDN documentation.