Unreleased changes
These pages document main, which has changes 0.1.2 does not. npm install @evanion/token gives you 0.1.2, documented here. Values on these pages are produced by running the current source on every build.
Entropy and collisions
To determine the correct length for the codes your system will issue, you can use the entropyBits property on the token instance returned by createToken.
The Problem: The “Birthday Paradox” in Codes
When you generate random IDs, you don’t just worry about running out of possible values—you worry about collisions. A collision occurs when generate() produces a value that has already been issued to another user.
Because of the “Birthday Paradox,” collisions happen much sooner than you’d expect. For example, with 35 bits of entropy, you don’t need 34 billion codes to see a collision; there’s a 50% chance of a collision after only 218,000 codes.
Calculating Entropy
The entropy of a token is the total number of random bits it carries. Since the check character is computed from the body and isn’t random, the formula is:
entropyBits reports this figure automatically:
const = ();
.; // -> 35
({ : 12 }).; // -> 55
({ : 13, : 13 }).; // -> 60The default length of 8 and 32-character dictionary provide 35 bits. Increasing the length to 13 increases the entropy to 60 bits.
Collision Probability
The 50% collision point is roughly . As you increase the length, the risk of collision drops exponentially:
length | Bits | 50% Collision Point |
|---|---|---|
| 6 | 25 | 6,800 |
| 8 | 35 | 218,000 |
| 10 | 45 | 7,000,000 |
| 13 | 60 | 1,300,000,000 |
Handling Collisions with Unique Indexes
@evanion/token does not check your database for collisions because it has no access to your issued codes. The responsibility for uniqueness lies with your database.
The best practice is to put a UNIQUE index on the body column and redraw the code if the insert fails:
const = ();
/** Stands in for the orders table's unique index on the code's body. */
const = new <string>();
function (: string): boolean {
if (.()) return false;
.();
return true;
}
function (): string {
for (let = 0; < 5; ++) {
const { , } = .({ : 'ORD' });
if (()) return ;
}
throw new ('could not allocate a pickup code');
}
().('ORD-'); // -> true
.; // -> 1In this pattern, you catch the unique violation error and call generate() again. The chance of five consecutive collisions at 35 bits is effectively zero (), making this a robust implementation.
Sizing Your Code Length
To choose the right length, calculate the chance that the next insert will conflict: .
A good rule of thumb is to keep this probability below 1 in 1,000 for your expected volume over the next three years. For a default 35-bit token, this threshold is reached at about 34 million issued codes.
Security Warning: Guessing vs. Typo-Detection
It is critical to understand that token codes are not secure credentials.
A 35-bit code is designed to protect against transcription errors (typos), not adversarial guessing. If a code grants high-value access (like a password reset or a gift card balance), it is susceptible to enumeration attacks.
- Wrong tool: Do not use
@evanion/tokenfor session IDs or security tokens. Use a 128-bit random value (UUID) for those. - Right tool: Use it for pickup codes, support tickets, or any identifier a human must handle.
If a readable code must gate a resource (like a gift card), you must implement additional security:
- Rate-limiting: Prevent attackers from guessing thousands of codes per second.
- Second-factor binding: Bind the code to a known identity, such as the customer’s email address.
Why not just make codes shorter?
You might wonder why we don’t just use a larger dictionary or drop the check character to save space.
- Alphabet Limits: Using a 64-character dictionary would require punctuation or non-ASCII characters. Even expanding to a full alphanumeric set (36 chars) would force us to include characters like
oandl, which are easily confused with0and1. We prioritize a “clean” alphabet to ensure human readability and transcription accuracy over raw density. - The Value of the Check: Dropping the check character would increase entropy (e.g., 40 bits instead of 35), but it would mean every mistyped code triggers a database lookup and a “Not Found” error.
The check character is the primary value proposition of the library; without it, you’re just generating random strings.