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

  1. Fix the issuer to emit kid as a JSON string
  2. Locally decode the token header (base64url of the first dot-segment) to confirm the kid type before debugging server-side
  3. 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

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


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/5cc1bd03cc29c0c1. Report an issue: GitHub.