@systemix/token
A cryptographically secure token generator and signed-token module for API keys, session tokens, CSRF tokens, and auth. Zero external dependencies, pure Node.js and browser built-ins. HMAC and RSA work in both environments.
Installation
pnpm add @systemix/token
npm install @systemix/token
Package Structure
| Subpath | Contents |
|---|---|
@systemix/token | Main entry — re-exports token, signed, rsa, common |
@systemix/token/token | Token generator, encoding utils, validation |
@systemix/token/signed | Signed token encode/decode/verify |
@systemix/token/rsa | RSA sign/verify (Node + browser, auto-detect) |
@systemix/token/common | Crypto, enums, types, errors, utils |
Token Generator
generateToken(props?)
Generates cryptographically secure random tokens. Returns a single string or array of strings.
import { generateToken } from '@systemix/token';
// Default: 32 bytes, hex encoding
const token = generateToken();
// → "a1b2c3d4e5f6789..."
// With options
const apiKey = generateToken({
byteLength: 32,
charset: 'hex',
});
const sessionToken = generateToken({
byteLength: 24,
charset: 'base64url',
});
const batch = generateToken({
byteLength: 16,
charset: 'alphanumeric',
count: 5,
});
// → ["Ab3xYz...", "Mn7pQr...", ...]
Props
| Property | Type | Default | Description |
|---|---|---|---|
byteLength | number | 32 | Number of random bytes (1–1024). Output length depends on charset. |
charset | 'hex' | 'base64' | 'base64url' | 'alphanumeric' | 'hex' | Encoding format. |
count | number | 1 | Number of tokens to generate (1–10). Returns string[] when > 1. |
Charset behavior
| Charset | Output length | Use case |
|---|---|---|
hex | byteLength × 2 | API keys, opaque IDs |
base64 | ~byteLength × 4/3 | Compact tokens |
base64url | ~byteLength × 4/3, URL-safe | URLs, cookies |
alphanumeric | byteLength | Human-readable, no special chars |
generateTokenPropValidation(props)
Validates props before generation. Throws if invalid.
import { generateTokenPropValidation } from '@systemix/token/token';
try {
generateTokenPropValidation({ byteLength: 64, charset: 'hex' });
} catch (e) {
console.error(e.message);
}
Encoding Utilities
Use when you need to encode raw bytes yourself:
import {
bytesToHex,
bytesToBase64,
bytesToBase64Url,
bytesToAlphanumeric,
} from '@systemix/token/token';
const bytes = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]);
bytesToHex(bytes); // → "48656c6c6f"
bytesToBase64(bytes); // → "SGVsbG8="
bytesToBase64Url(bytes); // → "SGVsbG8"
bytesToAlphanumeric(bytes); // → "NkF2bB2"
| Function | Input | Output |
|---|---|---|
bytesToHex(bytes) | Uint8Array | Hex string |
bytesToBase64(bytes) | Uint8Array | Base64 string |
bytesToBase64Url(bytes) | Uint8Array | URL-safe base64 |
bytesToAlphanumeric(bytes) | Uint8Array | A–Z, a–z, 0–9 |
Signed Tokens
Signed tokens are compact, URL-safe strings with a header, payload, and signature. Use for auth, sessions, or any signed claims. HMAC and RSA work in both Node and browser (Web Crypto API; RSA auto-detects environment).
encodeSigned(payload, secret, options?)
Creates a signed token. Returns a Promise<string>. Supports HMAC (shared secret) and RSA (PEM private key).
import { encodeSigned } from '@systemix/token/signed';
// HMAC (shared secret) — works in browser and Node
const token = await encodeSigned(
{ userId: '123', role: 'admin' },
'my-secret-key',
{
algorithm: 'HS256',
expiresIn: 3600,
issuer: 'my-app',
audience: 'api',
subject: 'user-123',
tokenId: true,
},
);
// RSA (PEM private key) — works in browser and Node
const tokenRsa = await encodeSigned({ userId: '123' }, privateKeyPem, {
algorithm: 'RS256',
expiresIn: 3600,
});
Encode options
| Option | Type | Default | Description |
|---|---|---|---|
algorithm | SignedAlgorithm | 'HS256' | Signing algorithm. |
typ | string | 'ST' | Token type in header. |
kid | string | — | Key ID for key rotation. |
cty | string | — | Content type. |
expiresIn | number | — | Expiration in seconds from now. |
notBefore | number | — | Not-before in seconds from now. |
issuedAt | number | now | Issued-at timestamp. |
issuer | string | — | iss claim. |
subject | string | — | sub claim. |
audience | string | string[] | — | aud claim. |
tokenId | string | true | — | jti claim. true = auto-generate. |
decodeSigned(token)
Decodes a token without verifying the signature.
import { decodeSigned } from '@systemix/token/signed';
const { header, payload, signature } = decodeSigned<{ userId: string }>(token);
console.log(header.alg); // "HS256"
console.log(payload.userId); // "123"
verifySigned(token, secret, options)
Decodes and verifies a token. algorithms is required to prevent algorithm confusion.
import { verifySigned } from '@systemix/token/signed';
const payload = await verifySigned<{ userId: string }>(token, 'my-secret', {
algorithms: ['HS256'],
issuer: 'my-app',
audience: 'api',
clockTolerance: 60,
});
Verify options
| Option | Type | Required | Description |
|---|---|---|---|
algorithms | SignedAlgorithm[] | Yes | Allowed algorithms. Prevents algorithm-swap attacks. |
issuer | string | string[] | No | Expected iss. |
audience | string | string[] | No | Expected aud. |
subject | string | No | Expected sub. |
clockTolerance | number | No | Seconds of skew for exp/nbf. |
ignoreExpiration | boolean | No | Skip exp check. |
ignoreNotBefore | boolean | No | Skip nbf check. |
Algorithms
| Algorithm | Type | Secret / key |
|---|---|---|
| HS256, HS384, HS512 | HMAC (symmetric) | Shared secret string |
| RS256, RS384, RS512 | RSA (asymmetric) | PEM private key (encode), PEM public key (verify) |
Standard claims
| Claim | Type | Description |
|---|---|---|
iss | string | Issuer |
sub | string | Subject |
aud | string | string[] | Audience |
exp | number | Expiration (Unix seconds) |
nbf | number | Not before (Unix seconds) |
iat | number | Issued at (Unix seconds) |
jti | string | Token ID |
Common Utilities
Low-level crypto primitives and constant-time comparison:
import {
getRandomBytes,
getRandomInt,
bytesEqual,
secureCompare,
} from '@systemix/token/common';
const bytes = getRandomBytes(32);
const n = getRandomInt(100); // [0, 100)
bytesEqual(a, b); // constant-time byte comparison
secureCompare(sig1, sig2); // constant-time string comparison
| Function | Description |
|---|---|
getRandomBytes(length) | Returns Uint8Array of random bytes. |
getRandomInt(max) | Returns random integer in [0, max). |
bytesEqual(a, b) | Constant-time byte comparison (timing-safe). |
secureCompare(a, b) | Constant-time string comparison (timing-safe). |
Common Module
For advanced use: enums, types, errors, and utils.
import {
CHARSETS,
HMAC_ALGORITHMS,
RSA_ALGORITHMS,
TokenPropsEnum,
type Charset,
type SignedAlgorithm,
} from '@systemix/token/common';
Error Handling
Token generator errors
Throws generic Error with messages like:
Invalid byteLength. Must be a positive number.Invalid charset. Must be one of: hex, base64, base64url, alphanumeric.
Signed token errors
All extend SignedTokenError:
| Error | When |
|---|---|
InvalidTokenError | Malformed token, invalid encoding |
InvalidSignatureError | Signature mismatch, wrong secret |
TokenExpiredError | exp in the past |
NotBeforeError | nbf in the future |
AudienceMismatchError | aud does not match |
IssuerMismatchError | iss does not match |
import {
verifySigned,
TokenExpiredError,
InvalidSignatureError,
} from '@systemix/token/signed';
try {
const payload = await verifySigned(token, secret, { algorithms: ['HS256'] });
} catch (e) {
if (e instanceof TokenExpiredError) {
console.log('Expired at', e.expiredAt);
} else if (e instanceof InvalidSignatureError) {
console.log('Invalid signature');
}
}
Usage Examples
API key generation
const apiKey = generateToken({
byteLength: 32,
charset: 'hex',
});
Session token with signed payload
const sessionToken = await encodeSigned(
{ userId: user.id, email: user.email },
process.env.SESSION_SECRET!,
{ expiresIn: 86400, issuer: 'my-app' },
);
const payload = await verifySigned(sessionToken, process.env.SESSION_SECRET!, {
algorithms: ['HS256'],
issuer: 'my-app',
});
RSA for distributed verification
const token = await encodeSigned({ sub: 'user-1' }, privateKey, {
algorithm: 'RS256',
expiresIn: 3600,
});
const payload = await verifySigned(token, publicKey, {
algorithms: ['RS256'],
});
Security Notes
- Algorithm whitelist: Always pass
algorithmstoverifySigned. Never trust the token header. - Secret strength: Use at least 256 bits (32 bytes) for HMAC secrets.
- RSA keys: Use 2048+ bit keys.
- Clock skew: Use
clockTolerancewhen servers may have time drift. - Sensitive data: Signed tokens are signed, not encrypted. Do not put secrets in the payload.
Try it
Open the Token Generator demo to experiment interactively.