{"record":{"id":"c2d827a5dfacd02a","repo":"honojs/hono","slug":"verifywithjwks-requires-options-for-either-keys","errorCode":null,"errorMessage":"verifyWithJwks requires options for either \"keys\" or \"jwks_uri\" or both","messagePattern":"verifyWithJwks requires options for either \"keys\" or \"jwks_uri\" or both","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/utils/jwt/jwt.ts","lineNumber":245,"sourceCode":"\n  let verifyKeys = options.keys ? [...options.keys] : undefined\n\n  if (options.jwks_uri) {\n    const response = await fetch(options.jwks_uri, init)\n    if (!response.ok) {\n      throw new Error(`failed to fetch JWKS from ${options.jwks_uri}`)\n    }\n    const data = (await response.json()) as { keys?: JsonWebKey[] }\n    if (!data.keys) {\n      throw new Error('invalid JWKS response. \"keys\" field is missing')\n    }\n    if (!Array.isArray(data.keys)) {\n      throw new Error('invalid JWKS response. \"keys\" field is not an array')\n    }\n    verifyKeys ??= []\n    verifyKeys.push(...(data.keys as HonoJsonWebKey[]))\n  } else if (!verifyKeys) {\n    throw new Error('verifyWithJwks requires options for either \"keys\" or \"jwks_uri\" or both')\n  }\n\n  const matchingKey = verifyKeys.find((key) => key.kid === header.kid)\n  if (!matchingKey) {\n    throw new JwtTokenInvalid(token)\n  }\n\n  // Verify that JWK's alg matches JWT header's alg when JWK has alg field\n  if (matchingKey.alg && matchingKey.alg !== header.alg) {\n    throw new JwtAlgorithmMismatch(matchingKey.alg, header.alg)\n  }\n\n  return await verify(token, matchingKey, {\n    alg: header.alg,\n    ...verifyOpts,\n  })\n}\n","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/honojs/hono/blob/e2740d5a1bd0b4254e517e3af8b60789284bc7bd/src/utils/jwt/jwt.ts#L227-L263","documentation":"verifyWithJwks was called with options containing neither a non-empty 'keys' array nor a 'jwks_uri'. The function needs at least one source of verification keys, so it fails fast before doing any work.","triggerScenarios":"verifyWithJwks(token, {}) or verifyWithJwks(token, { keys: [] }) with no jwks_uri; or a typo'd option name like { jwkUri: ... } or { key: [...] }.","commonSituations":"Building options dynamically and passing undefined; renaming options during a refactor; passing an empty keys array after filtering and forgetting the URI fallback.","solutions":["Pass at least one of options.keys (non-empty HonoJsonWebKey[]) or options.jwks_uri (string)","Check for typos in option names and that dynamic config is not undefined","If keys comes from dynamic filtering, ensure the fallback jwks_uri is included when it ends up empty"],"exampleFix":"// before\nverifyWithJwks(token, {})\n// after\nverifyWithJwks(token, { jwks_uri: 'https://auth.example.com/.well-known/jwks.json' })","handlingStrategy":"validation","validationCode":"const opts: VerifyJwksOptions = {}\nif (jwksUri) opts.jwks_uri = jwksUri\nif (keys?.length) opts.keys = keys\nif (!opts.jwks_uri && !opts.keys?.length) throw new Error('verification keys not configured')\nawait verifyWithJwks(token, opts)","typeGuard":"const hasKeySource = (o: { keys?: unknown[]; jwks_uri?: string }) =>\n  !!o.jwks_uri || (Array.isArray(o.keys) && o.keys.length > 0)","tryCatchPattern":"try { await verifyWithJwks(token, opts) } catch (e) { if ((e as Error).message.includes('either \"keys\" or \"jwks_uri\"')) failStartupConfigCheck(); throw e }","preventionTips":["Fail fast at boot if neither keys nor jwks_uri is configured","Type options as VerifyJwksOptions to catch typos at compile time","Log effective options before first verification"],"tags":["jwt","jwks","options","validation"],"backgroundTag":"missing-required-option","analyzedSha":"e2740d5a1bd0b4254e517e3af8b60789284bc7bd","analyzedAt":"2026-08-28T10:18:08.750Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}