createTokenVerifier
Signature
Section titled “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
Section titled “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
Section titled “secret”The secret used to sign and verify tokens. Must be at least 32 characters.
purpose
Section titled “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
Section titled “expiryMs”Token lifetime in milliseconds. After this duration, verifyToken returns null.
// Tokens expire in 15 minutesconst shortLivedTokens = createTokenVerifier({ secret: process.env.AUTH_SECRET!, purpose: 'password-reset', expiryMs: 15 * 60 * 1000,});
// Tokens expire in 24 hoursconst longLivedTokens = createTokenVerifier({ secret: process.env.AUTH_SECRET!, purpose: 'email-verification', expiryMs: 24 * 60 * 60 * 1000,});TokenVerifierInstance
Section titled “TokenVerifierInstance”createToken
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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 emailasync 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 passwordasync 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 };}import type { TokenVerifierConfig, TokenVerifierInstance,} from 'ideal-auth';