honojs/hono · error · HTTPException
invalid credentials structure
Error message
invalid credentials structure
What it means
When an Authorization header is present, the JWK middleware expects exactly the form 'Bearer <token>' (case-insensitive scheme, single space-separated into two parts). Any other structure — missing token, extra segments, or a different scheme — triggers a 401 HTTPException with error 'invalid_request' and description 'invalid credentials structure'.
Source
Thrown at src/middleware/jwk/jwk.ts:87
if (!options || !(options.keys || options.jwks_uri)) {
throw new Error('JWK auth middleware requires options for either "keys" or "jwks_uri" or both')
}
if (!crypto.subtle || !crypto.subtle.importKey) {
throw new Error('`crypto.subtle.importKey` is undefined. JWK auth middleware requires it.')
}
return async function jwk(ctx, next) {
const headerName = options.headerName || 'Authorization'
const credentials = ctx.req.raw.headers.get(headerName)
let token
if (credentials) {
const parts = credentials.split(/\s+/)
if (parts.length !== 2 || parts[0].toLowerCase() !== 'bearer') {
const errDescription = 'invalid credentials structure'
throw new HTTPException(401, {
message: errDescription,
res: unauthorizedResponse({
ctx,
error: 'invalid_request',
errDescription,
realm: options.realm,
}),
})
} else {
token = parts[1]
}
} else if (options.cookie) {
if (typeof options.cookie == 'string') {
token = getCookie(ctx, options.cookie)
} else if (options.cookie.secret) {
if (options.cookie.prefixOptions) {
token = await getSignedCookie(
ctx,View on GitHub (pinned to e2740d5a1b)
Solutions
- Make the client send 'Authorization: Bearer <jwt>' with exactly one space and no extra parts
- If using a custom header carrying the raw token, note the middleware still expects the Bearer prefix — either prepend 'Bearer ' client-side or pre-process the header
- Return/inspect the WWW-Authenticate response to distinguish this structural 401 from an invalid token
- Add a client-side check that the header matches /^Bearer \S+$/ before sending
Example fix
// before
fetch('/api', { headers: { Authorization: token } })
// after
fetch('/api', { headers: { Authorization: `Bearer ${token}` } }) Defensive patterns
Strategy: validation
Validate before calling
const isBearerHeader = (h: string | null): boolean =>
!!h && /^bearer\s+\S+$/i.test(h)
// client-side, before fetch:
if (!isBearerHeader(`Bearer ${token}`)) throw new Error('bad header') Type guard
const isWellFormedBearer = (credentials: string | null | undefined): credentials is string => !!credentials && credentials.split(/\s+/).length === 2 && credentials.split(/\s+/)[0].toLowerCase() === 'bearer'
Try / catch
try { await fetch(url, { headers: { Authorization: `Bearer ${token}` } }) } catch (e) { if (e instanceof HTTPException && e.status === 401) { /* inspect res body for invalid_request vs invalid_token */ } } Prevention
- Always format headers as `Bearer ${token}` with template literals
- Never send Basic auth or raw API keys to JWK-protected routes
- Add a client-side regex check /^Bearer \S+$/ before sending
- Log the WWW-Authenticate response header to distinguish structural errors from bad tokens
When it happens
Trigger: Requests with headers like 'Authorization: Bearer' (no token), 'Bearer a b c' (extra whitespace-separated parts), 'Basic dXNlcjpwYXNz', or 'token' alone; also custom headerName values where the client sends a non-Bearer format.
Common situations: Clients sending Basic auth or API keys to an endpoint expecting JWTs; custom auth schemes when headerName was changed (e.g. a proxy injecting X-Auth-Token with the raw token and no Bearer prefix); malformed hand-rolled client code concatenating tokens incorrectly.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- no authorization included in request
- Unauthorized
- invalid credentials structure
- JWK auth middleware requires options for either "keys" or "j
- `crypto.subtle.importKey` is undefined. JWK auth middleware
AI-assisted analysis of honojs/hono@e2740d5a1b (2026-08-28).
Data as JSON: /api/errors/ac4743c18787814d.
Report an issue: GitHub.