JavaScript code guide

JavaScript Password Generator

Build a secure password generator in JavaScript with the Web Crypto API, unbiased character selection, and explicit policy checks.

Secure JavaScript password generator

This browser function generates a 20-character password containing lowercase, uppercase, numeric, and symbol characters.

password-generator.js
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.

  1. cutoff finds the largest multiple of the alphabet length below the 32-bit range.
  2. Values at or above that cutoff are discarded.
  3. 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.

index.html
<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>
styles.css
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:

password-generator.mjs
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));
Protect the output. Do not write real credentials to browser analytics, console logs, CI logs, support tickets, or source control. Deliver generated values through the intended account or secret-management workflow.

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.

Executable policy checks
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.length introduces 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: use textContent for 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.