Secure Token Generator — JavaScript source
Generate cryptographically-secure random tokens in your browser. Pick the entropy size and format - hex, base32, base64, base62, or alphanumeric - and see the real strength in bits. Runs entirely client-side.
This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
// ─────────────────────────────────────────────────────────────────────────────
// Secure token generator - JavaScript polyglot showcase port.
// Language: JavaScript (ES module, ES2020+).
//
// CosmoDev polyglot showcase port of `token-generator`, ported from the
// canonical TypeScript logic in src/lib/token-generator.ts.
//
// This is display source - part of CosmoDev's polyglot tool pages, where each
// tool's pure logic is shown side-by-side in many languages. The live web tool
// runs the TypeScript lib; this file mirrors that logic for reference.
//
// Design:
// - Pure generation logic with an *injectable* RNG, so the path is unit-
// testable without touching Web Crypto. Production callers pass nothing
// and get the CSPRNG default; tests pass a seeded generator.
// - Each output symbol is selected without modulo bias via rejection
// sampling (see `constantTimeSelect`), so even non-power-of-two
// alphabets like base62 stay unbiased.
// - The CSPRNG uses the Web Crypto API (`globalThis.crypto`), available in
// browsers and in Node 19+ as a global. No external dependencies.
// ─────────────────────────────────────────────────────────────────────────────
/**
* The fixed alphabet strings. `custom` is intentionally absent here - its
* symbols are caller-supplied via `GenerateOptions.customAlphabet`.
*/
export const ALPHABETS = Object.freeze({
hex: '0123456789abcdef',
'hex-upper': '0123456789ABCDEF',
base32: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', // RFC 4648
'base32-crockford': '0123456789ABCDEFGHJKMNPQRSTVWXYZ', // Crockford (no I/L/O/U)
base64: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
base64url: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
base62: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
alphanumeric: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
});
/** Convenience encoding aliases that map 1:1 onto a fixed alphabet. */
const ENCODING_TO_ALPHABET = Object.freeze({
hex: 'hex',
base32: 'base32',
base64: 'base64',
base64url: 'base64url',
base62: 'base62',
alphanumeric: 'alphanumeric',
});
/**
* GenerateOptions (plain object - JS has no interface syntax):
* alphabet Output character set. Wins over `encoding` when both given.
* Pass 'custom' to supply symbols via `customAlphabet`.
* customAlphabet Used only when `alphabet === 'custom'`. Ignored otherwise.
* rng Uniform float generator in [0, 1). Defaults to the CSPRNG.
*/
/**
* Default CSPRNG-backed [0, 1) float, used when no `rng` is injected.
*
* Web Crypto fills a 32-bit unsigned integer with cryptographically secure
* randomness; dividing by 2^32 maps all 4 294 967 296 outcomes uniformly into
* the half-open unit interval - every value in [0, 1) is equally likely.
*/
function defaultRng() {
const buf = new Uint32Array(1);
crypto.getRandomValues(buf);
return buf[0] / 0x100000000; // 2^32
}
/**
* Resolve the effective alphabet string from options and an optional encoding.
*
* Precedence: explicit `alphabet` → `encoding` → `hex`. A `custom` alphabet
* with no/empty `customAlphabet` resolves to '' (an invalid, empty set), which
* downstream callers reject.
*/
export function resolveAlphabet(opts, encoding) {
if (opts?.alphabet === 'custom') return opts.customAlphabet ?? '';
if (opts?.alphabet) return ALPHABETS[opts.alphabet];
if (encoding) return ALPHABETS[ENCODING_TO_ALPHABET[encoding]];
return ALPHABETS.hex;
}
/**
* Build an `n`-character string from `alphabet`, selecting each symbol WITHOUT
* modulo bias via rejection sampling.
*
* Naive `draw % size` is biased whenever `size` does not divide the draw
* range: for base62 (size 62), the trailing symbols would be slightly
* over-represented. Instead we reject any 32-bit draw that lands in the
* uneven remainder (>= `limit`) and redraw - so every symbol is exactly
* equally likely. The `guard` cap keeps a pathological/constant `rng` from
* looping forever.
*/
export function constantTimeSelect(alphabet, n, rng = defaultRng) {
const size = alphabet.length;
if (size < 1 || !Number.isFinite(n) || n < 1) return '';
// Largest multiple of `size` that still fits in [0, 2^32-1]. Draws at or
// above this boundary map unevenly under `% size`, so we redraw.
const limit = Math.floor(0xffffffff / size) * size;
let out = '';
for (let i = 0; i < n; i++) {
let x = rng() * 0x100000000; // scale [0,1) up to [0, 2^32)
let guard = 0;
while (x >= limit && guard < 64) {
x = rng() * 0x100000000;
guard++;
}
out += alphabet[Math.floor(x) % size];
}
return out;
}
/** Output characters needed to carry `bytes` bytes of entropy through an
* alphabet of `alphabetSize` symbols. */
export function outputLength(bytes, alphabetSize) {
if (!Number.isFinite(bytes) || bytes < 1 || alphabetSize < 2) return 0;
return Math.ceil((bytes * 8) / Math.log2(alphabetSize));
}
/**
* Generate a token carrying `bytes` bytes of underlying entropy, rendered
* through `opts.alphabet` (or `encoding`). Each character is sampled
* uniformly without modulo bias, so the output is unbiased even for
* base62/alphanumeric. Returns '' for invalid input (non-positive or
* non-finite bytes, an alphabet of fewer than 2 symbols).
*
* Example: generateToken(16, { alphabet: 'hex' }) → 32 hex chars (128 bits).
*/
export function generateToken(bytes, opts, encoding) {
if (!Number.isFinite(bytes) || bytes < 1) return '';
const alphabet = resolveAlphabet(opts, encoding);
if (alphabet.length < 2) return '';
const n = outputLength(bytes, alphabet.length);
const rng = opts?.rng ?? defaultRng;
return constantTimeSelect(alphabet, n, rng);
}
/**
* Entropy (in bits) of a token of `bytes` entropy in a `size`-symbol alphabet.
*
* Equals `outputLength * log2(size)`, which is >= `bytes * 8` because the
* character count is rounded up to the next whole symbol.
*/
export function estimateEntropy(bytes, alphabetSize) {
if (!Number.isFinite(bytes) || bytes < 1 || alphabetSize < 2) return 0;
return outputLength(bytes, alphabetSize) * Math.log2(alphabetSize);
}
/**
* Bucket an entropy estimate (bits) into a human strength label.
* Tiers: weak <64 · fair 64-127 · strong 128-255 · very strong ≥256.
*/
export function strengthLabel(entropyBits) {
if (!Number.isFinite(entropyBits) || entropyBits < 64) return 'weak';
if (entropyBits < 128) return 'fair';
if (entropyBits < 256) return 'strong';
return 'very strong';
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →