Skip to content

createTokenVerifier

API reference for createTokenVerifier — signed expiring tokens.

Updated View as Markdown

Signature

function createTokenVerifier(config: TokenVerifierConfig): TokenVerifierInstance

Creates 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): string

Generates 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 } | null

Validates 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 milliseconds

Returns 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';
Navigation

Type to search…

↑↓ navigate↵ selectEsc close