honojs/hono · error · HTTPException

Unauthorized

Error message

Unauthorized

What it means

A token was supplied but verification (signature check against the JWKs, and related JWKS fetch/parse steps) failed, so the middleware throws a 401 HTTPException with error 'invalid_token'. A bare underlying Error (not a subclass) is rethrown as-is; otherwise the HTTPException includes 'token verification failure' in the response body.

Source

Thrown at src/middleware/jwk/jwk.ts:157

    let cause
    try {
      const keys = typeof options.keys === 'function' ? await options.keys(ctx) : options.keys
      const jwks_uri =
        typeof options.jwks_uri === 'function' ? await options.jwks_uri(ctx) : options.jwks_uri
      payload = await Jwt.verifyWithJwks(
        token,
        { keys, jwks_uri, verification: verifyOpts, allowedAlgorithms: options.alg },
        init
      )
    } catch (e) {
      cause = e
    }

    if (!payload) {
      if (cause instanceof Error && cause.constructor === Error) {
        throw cause
      }
      throw new HTTPException(401, {
        message: 'Unauthorized',
        res: unauthorizedResponse({
          ctx,
          error: 'invalid_token',
          statusText: 'Unauthorized',
          errDescription: 'token verification failure',
          realm: options.realm,
        }),
        cause,
      })
    }

    ctx.set('jwtPayload', payload)

    await next()
  }
}

View on GitHub (pinned to e2740d5a1b)

Solutions

  1. Confirm the token's issuer/kid matches a key in the configured JWKS; log the kid header and compare against the fetched key set
  2. If keys rotated, ensure jwks_uri is used (dynamic fetch) rather than stale static keys
  3. Verify network access to jwks_uri from the runtime (curl it from the same container) and that it returns valid JSON
  4. Check that the token itself is intact (not truncated or re-encoded) and, for Node runtimes, that fetch is available for JWKS retrieval

Example fix

// before
app.use(jwk({ keys: oldKeys })) // stale after IdP rotation
// after
app.use(jwk({ jwks_uri: 'https://issuer.example.com/.well-known/jwks.json' }))
Defensive patterns

Strategy: try-catch

Validate before calling

import { jwtVerify, createRemoteJWKSet } from 'jose' // optional pre-check
const precheck = async (token: string, jwks: any) => {
  try { await jwtVerify(token, jwks); return true } catch { return false }
}

Try / catch

try { return await handler(ctx, next) } catch (e) {
  if (e instanceof HTTPException && e.status === 401) {
    const body = await e.res?.clone().json().catch(() => null)
    if (body?.error === 'invalid_token') { /* key mismatch/rotation: log kid, refresh JWKS */ }
  }
  throw e
}

Prevention

When it happens

Trigger: Token signed with a different key than those in keys/jwks_uri (kid mismatch or rotated keys), a tampered or truncated JWT, an expired/malformed token surfaced as verification failure, or a failure fetching/parsing the JWKS endpoint (network error, invalid JSON).

Common situations: Key rotation on the IdP while the app cached old keys; wrong jwks_uri or environment mismatch (prod token against staging keys); clock skew causing validation errors; proxies stripping Authorization content; JWKS endpoint returning HTML error pages.

Understand the failure class

Related errors


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