[Solved] 'otplib' library throws TypeError: crypto2.createHmac is not a function error

I’m trying to use otplib library in cloudflare workers but seems like it is not supported because of a crypto/crypto2 dependency, at least that’s what I understood from this error:

Error generating OTP: crypto2.createHmac is not a function
TypeError: crypto2.createHmac is not a function
    at Object.createDigest (core:user:portunus-worker-dev:2616:30)
    at hotpDigest (core:user:portunus-worker-dev:2991:24)
    at hotpToken (core:user:portunus-worker-dev:2994:45)
    at totpToken (core:user:portunus-worker-dev:3107:16)
    at authenticatorToken (core:user:portunus-worker-dev:3254:16)
    at Authenticator.generate (core:user:portunus-worker-dev:3267:18)
    at module.exports.getOTP (core:user:portunus-worker-dev:5109:38)
    at async Object.handle (core:user:portunus-worker-dev:2186:37)
    at async jsonError (core:user:portunus-worker-dev:2149:18) {
  stack: TypeError: crypto2.createHmac is not a function
  …jsonError (core:user:portunus-worker-dev:2149:18),
  message: crypto2.createHmac is not a function

The code:

// Auth handlers
module.exports.getOTP = async ({ query, url, headers, cf = {} }) => {
  const { user, origin } = query // user is email
  if (!user || !EMAIL_REGEXP.test(user)) {
    return respondError(new HTTPError('User email not supplied', 400))
  let u = await fetchUser(user)
  if (!u) {
    u = await createUser(user)
  } else if (!u.otp_secret) {
    // legacy user without otp_secret
    u.otp_secret = uuidv4()
    if (u.otp_secret === u.jwt_uuid) {
      // Note: this shouldn't happen anyway
      throw new Error('OTP secret and JWT UUID are the same')
    u.updated = new Date()
    await updateUser(u)

  let otp;
  try {
    otp = totp.generate(u.otp_secret);
  } catch (err) {
    console.error("Error generating OTP:", err.message);
    throw err; // you can choose to rethrow the error, or handle it here and not throw

  const expiresAt = new Date(Date.now() + totp.timeRemaining() * 1000)
  // TODO: tricky for local dev as the origin is mapped to remote cloudflare worker
  const { origin: _origin } = new URL(url)
  const defaultOrigin = `${_origin}/login`
  // obtain locale and timezone from request for email `expiresAt` formatting
  const locale = (headers.get('Accept-Language') || '').split(',')[0] || 'en'
  const timeZone = cf.timezone || 'UTC'
  const plainEmail = [
    `OTP: ${otp}`,
    `Magic-Link: ${origin || defaultOrigin}?user=${user}&otp=${otp}`,
    `Expires at: ${expiresAt.toLocaleString(locale, {
      timeZoneName: 'long',
  await fetch('https://api.sendgrid.com/v3/mail/send', {
    method: 'POST',
    headers: {
      authorization: `Bearer ${MAIL_PASS}`,
      'content-type': 'application/json',
    body: JSON.stringify({
      personalizations: [{ to: [{ email: u.email }] }],
      from: { email: '[email protected]' },
      subject: 'OTP',
      content: [
          type: 'text/plain',
          value: plainEmail,
  return respondJSON({ payload: { message: `OTP/Magic-Link sent to ${user}` } })

I tried looking for a supported library for the same task but unable to find one.

Any help is appreciated.

3 posts were merged into an existing topic: [Solved] Can’t make the jwt create/sign/verify work in cloudflare workers