honojs/hono · critical · JwtTokenSignatureMismatched

token(${token}) signature mismatched

Error message

token(${token}) signature mismatched

What it means

Thrown after cryptographic verification fails: the signature bytes in the token do not match the header+payload when verified with the provided public key and algorithm. The token is tampered with or signed with a different key.

Source

Thrown at src/utils/jwt/jwt.ts:184

        ? aud.test(payloadAud)
        : typeof aud === 'string'
          ? payloadAud === aud
          : Array.isArray(aud) && aud.includes(payloadAud)
    )
    if (!matched) {
      throw new JwtTokenAudience(aud, payload.aud)
    }
  }

  const headerPayload = token.substring(0, token.lastIndexOf('.'))
  const verified = await verifying(
    publicKey,
    alg,
    decodeBase64Url(tokenParts[2]),
    utf8Encoder.encode(headerPayload)
  )
  if (!verified) {
    throw new JwtTokenSignatureMismatched(token)
  }

  return payload
}

// Symmetric algorithms that are not allowed for JWK verification
const symmetricAlgorithms: SymmetricAlgorithm[] = [
  AlgorithmTypes.HS256,
  AlgorithmTypes.HS384,
  AlgorithmTypes.HS512,
]

export const verifyWithJwks = async (
  token: string,
  options: {
    keys?: HonoJsonWebKey[]
    jwks_uri?: string
    verification?: VerifyOptions

View on GitHub (pinned to e2740d5a1b)

Solutions

  1. Confirm the public key/JWK corresponds to the private key that signed the token (check kid against JWKS)
  2. Ensure the alg passed to verify matches the token header's alg
  3. Re-fetch JWKS if keys may have rotated
  4. Reject the request: a signature mismatch may indicate tampering — do not retry with looser settings
  5. Check the token is not being altered (e.g. URL-decoding issues, whitespace)

Example fix

// before
await verify(token, wrongKey, { alg: 'RS256' }) // header says RS384
// after
const header = decodeHeader(token)
await verify(token, correctKey, { alg: header.alg })
Defensive patterns

Strategy: try-catch

Validate before calling

const header = decodeHeader(token)
if (header.alg !== opts.alg) throw new Error('alg mismatch before verify')

Try / catch

try { await verify(token, key, { alg }) } catch (e) { if (e instanceof JwtTokenSignatureMismatched) return unauthorized('invalid signature'); throw e }

Prevention

When it happens

Trigger: verify() computes verifying(publicKey, alg, signature, headerPayload) which returns false — wrong public key, wrong alg parameter, or a forged/modified token.

Common situations: Using the wrong JWKS/public key after key rotation; alg option not matching the token header's alg; token truncated or modified in transit; using a staging key against prod tokens; base64url corruption from URL encoding issues.

Related errors


AI-assisted analysis of honojs/hono@e2740d5a1b (2026-08-28). Data as JSON: /api/errors/44e77fc638cce2ac. Report an issue: GitHub.