honojs/hono · error · Error
invalid JWKS response. "keys" field is missing
Error message
invalid JWKS response. "keys" field is missing
What it means
verifyWithJwks fetched the JWKS URI successfully but the parsed JSON body has no 'keys' property. The JWKS format (RFC 7517) requires a top-level 'keys' array, so the library refuses to use the document. This usually means the URL points at something that is not a JWKS endpoint.
Source
Thrown at src/utils/jwt/jwt.ts:237
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}`)
}
const data = (await response.json()) as { keys?: JsonWebKey[] }
if (!data.keys) {
throw new Error('invalid JWKS response. "keys" field is missing')
}
if (!Array.isArray(data.keys)) {
throw new Error('invalid JWKS response. "keys" field is not an array')
}
verifyKeys ??= []
verifyKeys.push(...(data.keys as HonoJsonWebKey[]))
} else if (!verifyKeys) {
throw new Error('verifyWithJwks requires options for either "keys" or "jwks_uri" or both')
}
const matchingKey = verifyKeys.find((key) => key.kid === header.kid)
if (!matchingKey) {
throw new JwtTokenInvalid(token)
}
// Verify that JWK's alg matches JWT header's alg when JWK has alg field
if (matchingKey.alg && matchingKey.alg !== header.alg) {
throw new JwtAlgorithmMismatch(matchingKey.alg, header.alg)View on GitHub (pinned to e2740d5a1b)
Solutions
- Verify the value of options.jwks_uri is the 'jwks_uri' field from the provider's /.well-known/openid-configuration, not the issuer
- curl the JWKS URI and confirm the body is {"keys":[...]}
- If a proxy intercepts the request, fix routing or add the correct Host/Authorization headers so the real JWKS is returned
- Pass keys directly via options.keys as a fallback instead of fetching
Example fix
// before
await verifyWithJwks(token, { jwks_uri: 'https://auth.example.com' })
// after
const disc = await (await fetch('https://auth.example.com/.well-known/openid-configuration')).json()
await verifyWithJwks(token, { jwks_uri: disc.jwks_uri }) Defensive patterns
Strategy: validation
Validate before calling
const res = await fetch(jwksUri)
const data = await res.json()
if (!('keys' in data)) throw new Error('endpoint is not a JWKS document')
await verifyWithJwks(token, { keys: data.keys }) Type guard
const isJwks = (d: unknown): d is { keys: JsonWebKey[] } =>
typeof d === 'object' && d !== null && 'keys' in d && Array.isArray((d as any).keys) Try / catch
try { await verifyWithJwks(token, { jwks_uri }) } catch (e) { if (e instanceof Error && e.message.includes('JWKS')) { /* log URI, alert config issue */ } throw e } Prevention
- Always source jwks_uri from the OIDC discovery document
- Monitor JWKS fetch responses in dev (log status + body shape)
- Add integration tests hitting a mocked JWKS with correct shape
When it happens
Trigger: Calling verifyWithJwks({ jwks_uri }) where the URI returns valid JSON without a 'keys' field, e.g. an OIDC discovery document, an ID-token endpoint, an HTML-to-JSON error page, or a misrouted API response.
Common situations: Using the issuer URL instead of the jwks_uri from the discovery document; auth provider changed its JWKS path; a proxy/gateway returns a JSON error object (e.g. {"error":"not found"}) with status 200; typo in the JWKS URL.
Related errors
- required "kid" in jwt header: ${JSON.stringify(header)}
- symmetric algorithm "${alg}" is not allowed for JWK verifica
- 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/6cdef706ba315810.
Report an issue: GitHub.