{"record":{"id":"b4ae1cff867d2af7","repo":"siyuan-note/siyuan","slug":"verify-oidc-id-token-failed-w","errorCode":null,"errorMessage":"verify OIDC ID token failed: %w","messagePattern":"verify OIDC ID token failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/oidc_provider/provider.go","lineNumber":105,"sourceCode":"\t}\n\treturn p.oauth2Config.AuthCodeURL(state, oidc.Nonce(nonce), oauth2.S256ChallengeOption(codeVerifier))\n}\n\nfunc (p *Provider) Exchange(ctx context.Context, code, codeVerifier, nonce string) (map[string]any, error) {\n\ttoken, err := p.oauth2Config.Exchange(ctx, code, oauth2.VerifierOption(codeVerifier))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"exchange OIDC authorization code failed: %w\", err)\n\t}\n\tif p.kind == conf.OIDCProviderGitHub {\n\t\treturn exchangeGitHubClaims(ctx, token)\n\t}\n\trawIDToken, ok := token.Extra(\"id_token\").(string)\n\tif !ok || rawIDToken == \"\" {\n\t\treturn nil, errors.New(\"OIDC response does not contain an ID token\")\n\t}\n\tidToken, err := p.verifier.Verify(ctx, rawIDToken)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"verify OIDC ID token failed: %w\", err)\n\t}\n\tif idToken.Nonce != nonce {\n\t\treturn nil, errors.New(\"OIDC nonce does not match\")\n\t}\n\tclaims := map[string]any{}\n\tif err = idToken.Claims(&claims); err != nil {\n\t\treturn nil, fmt.Errorf(\"decode OIDC claims failed: %w\", err)\n\t}\n\treturn claims, nil\n}\n\nfunc newGitHub(config *conf.OIDC, redirectURL string) *Provider {\n\tscopes := append([]string{}, config.Scopes...)\n\tif len(scopes) == 0 || isDefaultOIDCScopes(scopes) {\n\t\tscopes = []string{\"read:user\", \"user:email\"}\n\t} else {\n\t\tfiltered := scopes[:0]\n\t\tfor _, scope := range scopes {","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/oidc_provider/provider.go#L87-L123","documentation":"Exchange verifies the returned ID token's signature, issuer, audience and expiry with the discovery-derived verifier; a failed verification is wrapped as \"verify OIDC ID token failed\". The token is therefore untrusted (bad signature, wrong issuer/audience, expired, or malformed) and sign-in is refused rather than accepting unverified claims.","triggerScenarios":"Calling Exchange when p.verifier.Verify(ctx, rawIDToken) fails: token signed by a key not in the JWKS, issuer mismatch between discovery URL and token iss, client_id not in aud, token expired (clock skew), or malformed/alg-mismatched token.","commonSituations":"Container clock skew making fresh tokens look expired; wrong issuer/realm configured so the token's iss differs from the discovered issuer; IdP rotated signing keys while the cached JWKS was stale; multi-tenant endpoints (e.g. Microsoft) issuing tenant-specific issuers; audience mismatch after changing the registered app ID.","solutions":["Read the wrapped cause — go-oidc names the exact problem (expired, signature, issuer, audience).","Synchronize the server clock (NTP) to eliminate expiry-related rejections.","Make the configured IssuerURL exactly match the iss claim of the issued tokens (correct realm/tenant path).","Confirm client_id/audience matches the app registered at the IdP and that the token's signing keys are served at the advertised jwks_uri and reachable from the kernel.","Retry sign-in after IdP key rotation so a fresh JWKS is fetched."],"exampleFix":"// before\ncfg := &conf.OIDC{Provider: conf.OIDCProviderCustom, IssuerURL: \"https://sso.example.com/realms/old\"} // token iss = .../realms/new\nprovider, err := New(cfg, redirectURL)\n// after\ncfg := &conf.OIDC{Provider: conf.OIDCProviderCustom, IssuerURL: \"https://sso.example.com/realms/new\"} // matches token iss\nprovider, err := New(cfg, redirectURL)","handlingStrategy":"try-catch","validationCode":"// pre-check clock skew and issuer reachability before sign-in\nif skew := time.Now().Sub(time.Now().UTC()); skew > 2*time.Minute || skew < -2*time.Minute {\n    return errors.New(\"server clock skew too large for JWT validation\")\n}","typeGuard":null,"tryCatchPattern":"claims, err := provider.Exchange(ctx, code, verifier, nonce)\nif err != nil {\n    if strings.Contains(err.Error(), \"verify OIDC ID token failed\") {\n        // log the wrapped cause; alert on expired tokens (clock skew) vs signature/issuer mismatch\n    }\n    return err\n}","preventionTips":["Run NTP on all hosts running the kernel","Ensure the configured IssuerURL exactly equals the token's iss claim","Keep client_id/audience aligned with the IdP app registration","Allow JWKS refresh after IdP signing-key rotation"],"tags":["oidc","jwt","signature-verification","security"],"backgroundTag":"jwt-token-expired","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}