Tencent/WeKnora · error

id_token missing kid and JWKS contains multiple RSA signing

Error message

id_token missing kid and JWKS contains multiple RSA signing keys

What it means

When an id_token lacks a kid header, verification can only proceed unambiguously if the JWKS contains exactly one RSA key. With multiple RSA keys present the correct one cannot be chosen, so the service fails closed instead of guessing.

Source

Thrown at internal/application/service/user.go:1897

		if kid != "" && k.Kid != kid {
			continue
		}
		if _, err := k.rsaPublicKey(); err != nil {
			continue
		}
		usable = append(usable, k)
	}
	if kid != "" {
		if len(usable) == 0 {
			return nil, fmt.Errorf("no matching JWKS RSA key for kid %q", kid)
		}
		return usable[0].rsaPublicKey()
	}
	if len(usable) == 0 {
		return nil, errors.New("no matching JWKS key for id_token")
	}
	if len(usable) > 1 {
		return nil, errors.New("id_token missing kid and JWKS contains multiple RSA signing keys")
	}
	return usable[0].rsaPublicKey()
}

// fetchOIDCJWKS loads the provider's JWKS document over the SSRF-safe client.
func (s *userService) fetchOIDCJWKS(ctx context.Context, jwksURI string) (*oidcJWKS, error) {
	if err := validateOIDCEndpoint("jwks", jwksURI, true); err != nil {
		return nil, err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, jwksURI, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Accept", "application/json")

	resp, err := newOIDCHTTPClient().Do(req)
	if err != nil {
		return nil, err

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Fix the token issuer to include kid in the JOSE header (standard behavior for Auth0/Keycloak/Okta, etc.).
  2. Use only one active signing key on the provider, or
  3. If you control token generation, sign with the key whose kid you emit.
  4. For testing, generate tokens with the same kid that the test JWKS publishes.

Example fix

// before: token header {"alg":"RS256"}
// after: token header includes key id
{"alg":"RS256","kid":"key-1"}
Defensive patterns

Strategy: validation

Validate before calling

hdr := decodeHeader(idToken)
rsaKeys := countRSAKeys(jwks)
if hdr.Kid == "" && rsaKeys > 1 {
    return fmt.Errorf("token has no kid but JWKS has %d RSA keys; issuer must emit kid", rsaKeys)
}

Type guard

func unambiguousToken(idToken string, jwks *oidcJWKS) bool {
    kid := decodeHeader(idToken).Kid
    if kid != "" { return true }
    n := 0
    for _, k := range jwks.Keys { if k.Kty == "RSA" { n++ } }
    return n == 1
}

Prevention

When it happens

Trigger: Token header has no kid, rsaKeyForKid skips the kid-match branch, and `usable` contains 2+ RSA keys from the JWKS.

Common situations: Provider rotates between several active signing keys but omits kid from token headers (provider bug/misconfiguration); custom token issuer that doesn't set kid; test tokens generated without a kid header.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/6de998e5726ab80f. Report an issue: GitHub.