{"record":{"id":"c935d5b7166a1334","repo":"Tencent/WeKnora","slug":"jwks-document-contains-no-keys","errorCode":null,"errorMessage":"JWKS document contains no keys","messagePattern":"JWKS document contains no keys","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/application/service/user.go","lineNumber":1928,"sourceCode":"\t}\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tresp, err := newOIDCHTTPClient().Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\t_, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 2048))\n\t\treturn nil, fmt.Errorf(\"JWKS request failed: status=%d\", resp.StatusCode)\n\t}\n\n\tvar jwks oidcJWKS\n\tif err := json.NewDecoder(io.LimitReader(resp.Body, 1<<20)).Decode(&jwks); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode JWKS document: %w\", err)\n\t}\n\tif len(jwks.Keys) == 0 {\n\t\treturn nil, errors.New(\"JWKS document contains no keys\")\n\t}\n\treturn &jwks, nil\n}\n\nconst oidcIDTokenLeeway = 2 * time.Minute\n\n// verifyOIDCIDToken cryptographically verifies an OIDC id_token: it checks the\n// RSA signature against the provider's JWKS (matched by kid) and validates the\n// issuer, audience (client_id), expiry and subject. It returns the verified claims.\nfunc (s *userService) verifyOIDCIDToken(\n\tctx context.Context, cfg *config.OIDCAuthConfig, idToken string,\n) (map[string]interface{}, error) {\n\tif strings.TrimSpace(cfg.JwksURI) == \"\" {\n\t\treturn nil, errors.New(\"cannot verify OIDC id_token: no jwks_uri configured\")\n\t}\n\tif strings.TrimSpace(cfg.IssuerURL) == \"\" {\n\t\treturn nil, errors.New(\"cannot verify OIDC id_token: issuer is not configured\")\n\t}","sourceCodeStart":1910,"sourceCodeEnd":1946,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/user.go#L1910-L1946","documentation":"fetchOIDCJWKS successfully fetched and decoded the JWKS document (within a 1MB limit), but the top-level keys array was empty. A JWKS with no keys cannot verify any token, so this is treated as a hard failure.","triggerScenarios":"GET jwks_uri returns valid JSON like {\"keys\": []} — json decode succeeds, len(jwks.Keys)==0.","commonSituations":"Provider has revoked/deleted all signing keys; jwks_uri points at an empty placeholder document; a mock/stub server returns an empty JWKS; fresh deployment where keys haven't been generated yet.","solutions":["Point JwksURI at the provider's real JWKS (from discovery metadata).","Check the provider's key management — ensure at least one active signing key exists.","If using a stub/mock for tests, populate the keys array with a valid RSA JWK.","Retry later if the provider is mid-rotation with a temporarily empty key set."],"exampleFix":"// before (stub response)\n{\"keys\": []}\n// after\n{\"keys\": [{\"kty\":\"RSA\",\"kid\":\"k1\",\"use\":\"sig\",\"n\":\"...\",\"e\":\"AQAB\"}]}","handlingStrategy":"validation","validationCode":"var doc struct{ Keys []json.RawMessage `json:\"keys\"` }\njson.Unmarshal(body, &doc)\nif len(doc.Keys) == 0 {\n    return fmt.Errorf(\"jwks_uri %s returned an empty key set\", cfg.JwksURI)\n}","typeGuard":"func jwksHasKeys(body []byte) bool {\n    var d struct{ Keys []json.RawMessage `json:\"keys\"` }\n    return json.Unmarshal(body, &d) == nil && len(d.Keys) > 0\n}","tryCatchPattern":null,"preventionTips":["Verify jwks_uri returns a populated key set during setup (curl + jq '.keys | length').","Ensure the IdP has at least one active signing key.","Populate test/stub JWKS with a valid RSA key."],"tags":["jwks","oidc","empty-keys","configuration"],"backgroundTag":"jwks-empty-keyset","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}