honojs/hono · error · JwtHeaderRequiresKid
required "kid" in jwt header: ${JSON.stringify(header)}
Error message
required "kid" in jwt header: ${JSON.stringify(header)} What it means
Thrown by verifyWithJwks when the JWT header is structurally valid but has no kid (key ID) claim. JWK-based verification requires kid to select which key from the JWKS/keys list is used.
Source
Thrown at src/utils/jwt/jwt.ts:215
export const verifyWithJwks = async (
token: string,
options: {
keys?: HonoJsonWebKey[]
jwks_uri?: string
verification?: VerifyOptions
allowedAlgorithms: readonly AsymmetricAlgorithm[]
},
init?: RequestInit
): Promise<JWTPayload> => {
const verifyOpts = options.verification || {}
const header = decodeHeader(token)
if (!isTokenHeader(header)) {
throw new JwtHeaderInvalid(header)
}
if (!header.kid) {
throw new JwtHeaderRequiresKid(header)
}
// Reject symmetric algorithms (HS256, HS384, HS512) to prevent algorithm confusion attacks
if (symmetricAlgorithms.includes(header.alg as SymmetricAlgorithm)) {
throw new JwtSymmetricAlgorithmNotAllowed(header.alg)
}
// Validate against allowed algorithms
if (!options.allowedAlgorithms.includes(header.alg as AsymmetricAlgorithm)) {
throw new JwtAlgorithmNotAllowed(header.alg, options.allowedAlgorithms)
}
let verifyKeys = options.keys ? [...options.keys] : undefined
if (options.jwks_uri) {
const response = await fetch(options.jwks_uri, init)
if (!response.ok) {
throw new Error(`failed to fetch JWKS from ${options.jwks_uri}`)View on GitHub (pinned to e2740d5a1b)
Solutions
- If the token is symmetric/shared-secret, use verify() with the secret key instead of verifyWithJwks
- If using an asymmetric issuer, ensure it includes kid in the JOSE header (most OIDC providers do)
- If you already know the exact key, pass it to verify() directly and skip JWKS lookup
- Check whether the JWKS has only one key — some libraries allow kid-less selection, but this one requires kid
Example fix
// before
await verifyWithJwks(hs256TokenWithoutKid, opts) // throws
// after
await verify(hs256TokenWithoutKid, secret, { alg: 'HS256' }) Defensive patterns
Strategy: validation
Validate before calling
const header = decodeHeader(token)
if (!header.kid) { /* route to verify() with shared secret or reject */ } Type guard
const hasKid = (h: unknown): h is { kid: string } =>
typeof h === 'object' && h !== null && typeof (h as { kid?: unknown }).kid === 'string' Try / catch
try { await verifyWithJwks(token, opts) } catch (e) { if (e instanceof JwtHeaderRequiresKid) return unauthorized('missing kid'); throw e } Prevention
- Use verifyWithJwks only for asymmetric tokens with kid
- Keep symmetric tokens on verify() with the secret
When it happens
Trigger: verifyWithJwks(token, options) on a token whose header omits kid — e.g. a symmetric-HS256 token or a custom-minted token without kid.
Common situations: Using verifyWithJwks for tokens signed with a shared secret (no kid); tokens from a legacy issuer that doesn't set kid; testing with manually crafted tokens; confusion between verify() (single fixed key) and verifyWithJwks() (key lookup by kid).
Related errors
- symmetric algorithm "${alg}" is not allowed for JWK verifica
- invalid JWKS response. "keys" field is missing
- invalid JWKS response. "keys" field is not an array
- verifyWithJwks requires options for either "keys" or "jwks_u
- JWK auth middleware requires options for either "keys" or "j
AI-assisted analysis of honojs/hono@e2740d5a1b (2026-08-28).
Data as JSON: /api/errors/602957e63ef4ccf6.
Report an issue: GitHub.