ory/hydra · error
jwt from authorization HTTP header is expecting string value
Error message
jwt from authorization HTTP header is expecting string value for "kid" in tokenWithoutKid header but got: %T
What it means
In the same key-resolution callback, the kid header value must be a JSON string. If token.Header["kid"] exists but is not a string (e.g. a number or object produced by a broken issuer), this typed error reports the Go type that was found.
Source
Thrown at oryx/jwtmiddleware/middleware.go:106
ErrorWriter: herodot.NewJSONWriter(nil),
}
for _, o := range opts {
o(c)
}
jc := jwksx.NewFetcher(wellKnownURL)
return &Middleware{
o: c,
wku: wellKnownURL,
jm: jwtmiddleware.New(
func(ctx context.Context, rawToken string) (any, error) {
return jwt.NewParser(
jwt.WithValidMethods([]string{c.SigningMethod.Alg()}),
).Parse(rawToken, func(token *jwt.Token) (interface{}, error) {
if raw, ok := token.Header["kid"]; !ok {
return nil, errors.New(`jwt from authorization HTTP header is missing value for "kid" in token header`)
} else if kid, ok := raw.(string); !ok {
return nil, fmt.Errorf(`jwt from authorization HTTP header is expecting string value for "kid" in tokenWithoutKid header but got: %T`, raw)
} else if k, err := jc.GetKey(kid); err != nil {
return nil, err
} else {
return k.Key, nil
}
})
},
jwtmiddleware.WithCredentialsOptional(false),
jwtmiddleware.WithTokenExtractor(func(r *http.Request) (string, error) {
// wrapping the extractor to get a herodot.ErrorContainer
token, err := jwtmiddleware.AuthHeaderTokenExtractor(r)
if err != nil {
return "", herodot.ErrUnauthorized().WithReason(err.Error())
}
return token, nil
}),
jwtmiddleware.WithErrorHandler(func(w http.ResponseWriter, r *http.Request, err error) {
switch {View on GitHub (pinned to 4174065ffb)
Solutions
- Fix the issuer to emit kid as a JSON string
- Locally decode the token header (base64url of the first dot-segment) to confirm the kid type before debugging server-side
- Re-encode/reissue the offending token; reject such tokens early in the issuer pipeline
Example fix
// before (hand-crafted header)
{"alg":"ES256","kid":42}
// after
{"alg":"ES256","kid":"42"} Defensive patterns
Strategy: type-guard
Validate before calling
func kidIsString(rawJWT string) error {
parts := strings.Split(rawJWT, ".")
if len(parts) != 3 { return errors.New("malformed token") }
hdr, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil { return err }
var h map[string]any
if err := json.Unmarshal(hdr, &h); err != nil { return err }
if v, ok := h["kid"]; ok {
if _, isStr := v.(string); !isStr {
return fmt.Errorf("kid must be a JSON string, got %T", v)
}
}
return nil
} Type guard
func kidAsString(header map[string]any) (string, bool) {
raw, present := header["kid"]
if !present { return "", false }
kid, isStr := raw.(string)
return kid, isStr
} Try / catch
err := next(w, r.WithContext(ctx))
if err != nil && strings.Contains(err.Error(), "expecting string value for \"kid\"") {
http.Error(w, "token kid header must be a string", http.StatusUnauthorized)
} Prevention
- Fix non-conformant issuers that emit numeric kid values — RFC 7515 requires a string
- Decode and inspect token headers in integration tests before deploying clients
- Avoid hand-crafting JWT headers in scripts; use a maintained JWT library
- Add issuer-side schema validation of the JOSE header
When it happens
Trigger: A token whose kid header is a JSON number/bool/object — usually from a non-conformant token library or manual JWT construction (base64url-encoded header with "kid":123 instead of "kid":"123").
Common situations: Custom JWT minting code using numeric key ids; misconfigured issuer templates; debugging hand-crafted tokens in curl scripts.
Related errors
- ErrUnableToFindKeyID
- unable to find JSON Web Key with ID: %s
- a key ID must be specified when multiple JWK sets are config
- jwksx: "%s" does not support arbitrary key length
- jwksx: key size must be at least 2048 bit for algorithm "%s"
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/5cc1bd03cc29c0c1.
Report an issue: GitHub.