Signature
function createTokenVerifier(config: TokenVerifierConfig): TokenVerifierInstanceCreates a token verifier for generating and validating signed, expiring tokens. Useful for password reset links, email verification, magic links, and similar one-time-use flows.
import { createTokenVerifier } from 'ideal-auth';
const passwordResetTokens = createTokenVerifier({
secret: process.env.AUTH_SECRET!,
purpose: 'password-reset',
});TokenVerifierConfig
interface TokenVerifierConfig {
secret: string;
purpose: string;
expiryMs?: number;
}| Field | Type | Required | Default |
|---|---|---|---|
secret |
string |
Yes | — |
purpose |
string |
Yes | — |
expiryMs |
number (milliseconds) |
No | 3600000 (1 hour) |
secret
The secret used to sign and verify tokens. Must be at least 32 characters.
purpose
A label identifying the flow the tokens belong to (e.g. 'password-reset', 'email-verification', 'magic-link'). The purpose is mixed into the HMAC signature — it is not part of the token string — so a token only verifies against a verifier configured with the same purpose. This provides domain separation between flows without requiring a separate secret for each one.
expiryMs
Token lifetime in milliseconds. After this duration, verifyToken returns null.
// Tokens expire in 15 minutes
const shortLivedTokens = createTokenVerifier({
secret: process.env.AUTH_SECRET!,
purpose: 'password-reset',
expiryMs: 15 * 60 * 1000,
});
// Tokens expire in 24 hours
const longLivedTokens = createTokenVerifier({
secret: process.env.AUTH_SECRET!,
purpose: 'email-verification',
expiryMs: 24 * 60 * 60 * 1000,
});TokenVerifierInstance
createToken
createToken(userId: string): stringGenerates a signed token for the given user ID. The token encodes the user ID, a random identifier, creation time, expiration time, and an HMAC signature.
const tokens = createTokenVerifier({
secret: process.env.AUTH_SECRET!,
purpose: 'email-verification',
});
const token = tokens.createToken('user_abc123');
// "dXNlcl9hYmMxMjM.k8f3j2m1.1709136000000.1709139600000.a1b2c3d4..."Token format:
The token is a dot-separated string with 5 parts:
base64url(userId).randomId.iat.exp.signature| Part | Description |
|---|---|
base64url(userId) |
The user ID, base64url-encoded to support dots and special characters |
randomId |
A random identifier unique to this token |
iat |
Issued-at timestamp in milliseconds (Date.now() at creation) |
exp |
Expiration timestamp in milliseconds (iat + expiryMs) |
signature |
HMAC-SHA256 signature covering all four preceding fields plus the verifier’s purpose |
verifyToken
verifyToken(token: string): { userId: string; iatMs: number } | nullValidates a token’s signature and checks that it has not expired. Returns the decoded payload on success, or null if the token is invalid, expired, or tampered with.
const tokens = createTokenVerifier({
secret: process.env.AUTH_SECRET!,
purpose: 'email-verification',
});
const result = tokens.verifyToken(token);
if (!result) {
return { error: 'Invalid or expired token.' };
}
// result.userId — the user ID encoded in the token
// result.iatMs — creation timestamp in millisecondsReturns null when:
- The token format is malformed (wrong number of segments)
- The signature does not match (token was tampered with)
- The token was created by a verifier with a different
purpose - The token has expired (
Date.now() > exp)
Invalidation patterns
Tokens are stateless — there is no token store to revoke. Use the iatMs field to implement invalidation patterns without a database lookup per verification.
Invalidate after password change
Store a passwordChangedAt timestamp on the user. Reject tokens issued before the last password change.
async function handlePasswordReset(token: string, newPassword: string) {
const result = tokens.verifyToken(token);
if (!result) {
return { error: 'Invalid or expired token.' };
}
const user = await db.user.findUnique({
where: { id: result.userId },
});
if (!user) {
return { error: 'User not found.' };
}
// Reject if token was issued before the last password change
if (user.passwordChangedAt && result.iatMs < user.passwordChangedAt.getTime()) {
return { error: 'This token has already been used.' };
}
const hashedPassword = await hash.make(newPassword);
await db.user.update({
where: { id: user.id },
data: {
password: hashedPassword,
passwordChangedAt: new Date(),
},
});
return { success: true };
}Single-use tokens
Store the token’s iatMs or a nonce in the database and check it during verification.
async function verifyEmail(token: string) {
const result = tokens.verifyToken(token);
if (!result) {
return { error: 'Invalid or expired token.' };
}
const user = await db.user.findUnique({
where: { id: result.userId },
});
if (!user || user.emailVerifiedAt) {
return { error: 'Invalid token.' };
}
await db.user.update({
where: { id: user.id },
data: { emailVerifiedAt: new Date() },
});
return { success: true };
}Full example: password reset flow
import { createTokenVerifier, createHash } from 'ideal-auth';
const hash = createHash();
const passwordResetTokens = createTokenVerifier({
secret: process.env.AUTH_SECRET!,
purpose: 'password-reset',
expiryMs: 60 * 60 * 1000, // 1 hour
});
// Step 1: Generate a reset token and send it via email
async function requestPasswordReset(email: string) {
const user = await db.user.findUnique({ where: { email } });
if (!user) {
// Return success even if user doesn't exist to prevent enumeration
return { success: true };
}
const token = passwordResetTokens.createToken(user.id);
const resetUrl = `https://example.com/reset-password?token=${token}`;
await sendEmail(user.email, 'Reset your password', resetUrl);
return { success: true };
}
// Step 2: Verify the token and update the password
async function resetPassword(token: string, newPassword: string) {
const result = passwordResetTokens.verifyToken(token);
if (!result) {
return { error: 'Invalid or expired reset link.' };
}
const hashedPassword = await hash.make(newPassword);
await db.user.update({
where: { id: result.userId },
data: {
password: hashedPassword,
passwordChangedAt: new Date(),
},
});
return { success: true };
}Types
import type {
TokenVerifierConfig,
TokenVerifierInstance,
} from 'ideal-auth';