{"record":{"id":"690f0a907e2fe259","repo":"ory/hydra","slug":"a-key-id-must-be-specified-when-multiple-jwk-sets","errorCode":null,"errorMessage":"a key ID must be specified when multiple JWK sets are configured","messagePattern":"a key ID must be specified when multiple JWK sets are configured","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oryx/jwksx/fetcher_v2.go","lineNumber":98,"sourceCode":"// WithHTTPClient will use the given HTTP client to fetch the JSON Web Keys.\nfunc WithHTTPClient(c *retryablehttp.Client) FetcherNextOption {\n\treturn func(o *fetcherNextOptions) {\n\t\to.httpClient = c\n\t}\n}\n\nfunc (f *FetcherNext) ResolveKey(ctx context.Context, locations string, modifiers ...FetcherNextOption) (jwk.Key, error) {\n\treturn f.ResolveKeyFromLocations(ctx, []string{locations}, modifiers...)\n}\n\nfunc (f *FetcherNext) ResolveKeyFromLocations(ctx context.Context, locations []string, modifiers ...FetcherNextOption) (jwk.Key, error) {\n\topts := new(fetcherNextOptions)\n\tfor _, m := range modifiers {\n\t\tm(opts)\n\t}\n\n\tif len(locations) > 1 && opts.forceKID == \"\" {\n\t\treturn nil, errors.Errorf(\"a key ID must be specified when multiple JWK sets are configured\")\n\t}\n\n\tset := jwk.NewSet()\n\teg := new(errgroup.Group)\n\tfor k := range locations {\n\t\tlocation := locations[k]\n\t\teg.Go(func() error {\n\t\t\tremoteSet, err := f.fetch(ctx, location, opts)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\titerator := remoteSet.Iterate(ctx)\n\t\t\tfor iterator.Next(ctx) {\n\t\t\t\t// Pair().Value is always of type jwk.Key when generated by Iterate.\n\t\t\t\tset.Add(iterator.Pair().Value.(jwk.Key))\n\t\t\t}\n","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/jwksx/fetcher_v2.go#L80-L116","documentation":"ResolveKeyFromLocations fetches and merges JWK sets from multiple remote locations. When more than one location is given, the resulting merged set can contain many keys, so the library requires a key ID (via the WithKeyID/forceKID modifier) to know which key to return. If the caller passes multiple locations but no key ID, this error is returned before any fetching happens.","triggerScenarios":"Calling ResolveKeyFromLocations (directly or via ResolveKey) with a locations slice of length > 1 while omitting the modifier that sets opts.forceKID (e.g. WithKeyID).","commonSituations":"Configuring an app to trust keys from several JWKS endpoints (e.g. multiple OIDC providers or key rotation endpoints) but forgetting to pin a specific kid; a config file lists several jwks_urls but no expected kid; tests that pass a variadic list of URLs.","solutions":["Pass the key-ID modifier when resolving from multiple locations, e.g. ResolveKeyFromLocations(ctx, urls, jwksx.WithKeyID(\"my-kid\"))","Reduce the locations slice to a single JWKS URL if no specific kid is needed","If a specific key must be selected, ensure the token's kid header matches a key in the fetched sets and pass it as forceKID"],"exampleFix":"// before\nkey, err := fetcher.ResolveKeyFromLocations(ctx, []string{url1, url2})\n// after\nkey, err := fetcher.ResolveKeyFromLocations(ctx, []string{url1, url2}, jwksx.WithKeyID(\"my-key-id\"))","handlingStrategy":"validation","validationCode":"func resolveKeySafe(locations []string, modifiers ...jwksx.FetcherNextOption) error {\n\topts := &fetcherNextOptions{} // or track via your own wrapper struct\n\tfor _, m := range modifiers {\n\t\tm(opts)\n\t}\n\tif len(locations) > 1 && opts.forceKID == \"\" {\n\t\treturn errors.New(\"provide WithKeyID(...) when resolving from multiple JWKS locations\")\n\t}\n\treturn nil\n}","typeGuard":"func hasKeyID(modifiers []jwksx.FetcherNextOption) bool { // use a captured opts struct\n\tvar forceKID string\n\tfor _, m := range modifiers {\n\t\tm(&fetcherNextOptionsForCheck{})\n\t}\n\treturn forceKID != \"\"\n}","tryCatchPattern":"key, err := fetcher.ResolveKeyFromLocations(ctx, urls, opts...)\nif err != nil {\n\tif strings.Contains(err.Error(), \"a key ID must be specified\") {\n\t\t// fall back to single location or add WithKeyID\n\t}\n\treturn err\n}","preventionTips":["Always pair multi-location JWKS configs with an explicit kid","Centralize JWKS resolution in one helper that enforces the kid rule","Validate configuration at startup: N jwks_urls implies a configured kid","Keep the locations list to one entry when keys are not disambiguated by kid"],"tags":["jwks","go","configuration","validation"],"backgroundTag":"missing-jwks-key-id","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}