`jwt.sign()` callback is executed twice for "The payload already has an "..." property" errors

Author: pfgithubCreated Sep 10, 2025Updated Mar 30, 2026

Description

The callback to jwt.sign() is executed twice when there is an error Bad "options.issuer" option. The payload already has an "iss" property. I would expect the callback to only be executed once.

Reproduction

javascript
// a.mjs
import jwt from "jsonwebtoken";
jwt.sign(
  {
    iss: "bar",
    iat: 1757476476,
  },
  "secret",
  {
    algorithm: "HS256",
    issuer: "foo",
  },
  (err, asyncSigned) => {
    console.log("callback called:", asyncSigned ?? err?.message);
  },
);
$> npm init -y && npm add jsonwebtoken
$> node a.mjs
callback called: Bad "options.issuer" option. The payload already has an "iss" property.
callback called: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJiYXIiLCJpYXQiOjE3NTc0NzY0NzZ9.TkSecbZDocHnjwnuDhQRXNvRkxsERNGBkRc6P0wHlNY

Notice that "callback called" is printed twice, while I would expect it to be printed only once.

Environment

jsonwebtoken 9.0.2 on Darwin 24.6.0 arm64 arm

Other information

It looks like the error is here:

https://github.com/auth0/node-jsonwebtoken/blob/bc28861f1fa981ed9c009e29c044a19760a0b128/sign.js#L217-L225

Typically, return failure() throws which exits the forEach and the function. But when a callback is provided, it only returns from the forEach. The rest of the keys are checked and the rest of the function still runs.