[Solved] Can't make the jwt create/sign/verify work in cloudflare workers

I’ve been trying to make the jwt creation and signing work in cf workers but unable to do so.

I’ve used these modules/libraries with node but no luck so far:

  1. @tsndr/cloudflare-worker-jwt - npm
  2. jose - npm

It keeps on throwing:
"TypeError: crypto2.createHmac is not a function"

This is my package.json and I’m pretty sure that no other package is dependent on crypto/crypto2:

{
  "name": "portunus-worker",
  "version": "1.0.0",
  "description": "package for creating workers templates",
  "main": "index.js",
  "scripts": {
    "test": "jest",
    "format": "prettier --write '**/*.{js,css,jsone,md}'",
    "lint": "eslint *.js modules/*.js",
    "dev": "npx wrangler dev"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@cloudflare/wrangler": "^1.19.5",
    "eslint": "^8.5.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^4.0.0",
    "jest": "^27.4.5",
    "prettier": "^2.2.1"
  },
  "dependencies": {
    "@tsndr/cloudflare-worker-jwt": "^2.2.1",
    "deta-worker": "^0.2.0-alpha",
    "eslint-plugin-jest": "^25.3.4",
    "itty-router": "^2.5.2",
    "itty-router-extras": "^0.4.2",
    "jose": "^4.14.4",
    "otplib": "^12.0.1",
    "serverless-cloudflare-workers": "^1.2.0",
    "uuid": "^8.3.2"
  }
}

Not sure what I’m missing, any help is appreciated, thanks.

Can you share the code for your worker @ashishjullia19? I have used @tsndr/cloudflare-worker-jwt - npm without issue before.

@anon9246926
Here is the code: GitHub - portunus-dev/portunus-worker: edge workers as API servers

Wherever there are refs to jwt., I just switched the jsonwebtoken to this Library.

Did you await this per this example

const token = await jwt.sign({ name: 'John Doe', email: '[email protected]' }, 'secret')

Not sure where this is coming from as I don’t believe either package relies on this module.

@anon9246926
Yes, I did use await.

I’m not sure either from where this crypto2 error is coming from.

Seems like the problem is with otplib library, not sure how to solve this though as I’m unable to find a library which does no rely on crypto:

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
}

Also @anon9246926 I just created a new post/thread: 'otplib' library throws TypeError: crypto2.createHmac is not a function error

Solved this using: [Solved] 'otplib' library throws TypeError: crypto2.createHmac is not a function error - #3 by ashishjullia19

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)
  console.log(u.otp_secret)
  console.log(u)
  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, {
      timeZone,
      timeZoneName: 'long',
    })}`,
  ].join('\n')
  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.

I’ve solved this using: create-totp-cf-workers · GitHub