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!,});TokenVerifierConfig
Section titled “TokenVerifierConfig”interface TokenVerifierConfig { secret: string; expiryMs?: number;}| Field | Type | Required | Default |
|---|---|---|---|
secret | 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.
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!, expiryMs: 15 * 60 * 1000,});
// Tokens expire in 24 hoursconst longLivedTokens = createTokenVerifier({ secret: process.env.AUTH_SECRET!, 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!,});
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 |
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!,});
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 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! + ':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';