Tencent/WeKnora · error

no matching JWKS key for id_token

Error message

no matching JWKS key for id_token

What it means

During id_token verification, no RSA key in the fetched JWKS matched the token. If the token carried a kid, no key with that kid existed; if it carried no kid, the JWKS had no usable RSA keys at all. Verification cannot proceed without the provider's signing key.

Source

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

		if k.Kty != "" && !strings.EqualFold(k.Kty, "RSA") {
			continue
		}
		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")

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Confirm cfg.JwksURI matches the provider's advertised jwks_uri from discovery (.well-known/openid-configuration).
  2. Decode the id_token header and check its kid exists in the JWKS document.
  3. Re-fetch/refresh the JWKS — the key may have been rotated recently.
  4. Ensure the provider includes kid in token headers and publishes the corresponding key.

Example fix

// before
JwksURI: "https://old-provider.example.com/.well-known/jwks.json"
// after
JwksURI: discovery.Doc.JWKSURI // from the provider's openid-configuration
Defensive patterns

Strategy: retry

Validate before calling

// decode token header and pre-check kid against cached JWKS
parts := strings.Split(idToken, ".")
hdr := decodeHeader(parts[0])
if !jwksHasKid(jwks, hdr.Kid) {
    jwks = refreshJWKS(ctx, cfg.JwksURI) // key rotation?
}

Type guard

func jwksHasKid(j *oidcJWKS, kid string) bool {
    for _, k := range j.Keys {
        if k.Kid == kid && k.Kty == "RSA" { return true }
    }
    return false
}

Try / catch

key, err := svc.VerifyOIDCIDToken(ctx, cfg, idToken)
if err != nil && strings.Contains(err.Error(), "no matching JWKS") {
    // refresh JWKS once, then retry verification
    if rerr := svc.RefreshJWKS(ctx, cfg.JwksURI); rerr == nil {
        key, err = svc.VerifyOIDCIDToken(ctx, cfg, idToken)
    }
}

Prevention

When it happens

Trigger: verifyOIDCIDToken selects rsaKeyForKid(kid) (or the kid-less path) and the filtered `usable` slice is empty — token kid not present in JWKS, or JWKS has zero RSA keys when the token has no kid.

Common situations: Provider rotated signing keys and the configured jwks_uri points at a stale copy; jwks_uri misconfigured to the wrong provider's JWKS; token signed with a key not yet published; token missing kid while provider publishes multiple keys elsewhere.

Related errors


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