{"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/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/oidc_provider/provider.go#L87-L123","documentation":"Thrown by Provider.Exchange() when p.verifier.Verify(ctx, rawIDToken) fails. The go-oidc verifier validates the JWT signature against the provider's published JWKS, checks the issuer, audience, and expiry. Any signature mismatch, expired token, wrong audience, or JWKS fetch failure is wrapped with %w.","triggerScenarios":"Calling Exchange() where the id_token fails verification: the JWT signature does not match any key in the provider's JWKS, the token has expired (exp claim), the audience (aud) does not match the configured ClientID, the issuer (iss) does not match the discovery issuer, or the JWKS endpoint is unreachable.","commonSituations":"Clock skew between the SiYuan server and the provider causes premature expiry rejection. The provider rotated its signing keys but the cached JWKS is stale (go-oidc caches keys and may need a process restart). The ClientID used for verification differs from the one the token was issued for. The provider's clock is ahead, making the token appear expired.","solutions":["Check for clock skew: ensure NTP is running on the SiYuan host; a skew of even 30 seconds can cause rejection.","Verify the ClientID in SiYuan config matches the audience claim in the token.","If the provider rotated signing keys, restart the SiYuan kernel to refresh the JWKS cache.","Decode the JWT (base64) and inspect the exp, iss, aud claims manually to identify which check failed.","Inspect the wrapped error for go-oidc's specific message (e.g., 'oidc: id token signed by unsupported algorithm')."],"exampleFix":"// before\n// clock skew causing verification failure\n\n// after\n// Sync system clock\n// sudo ntpdate pool.ntp.org\n// or install chrony/NTP daemon\n\n// Alternatively, decode JWT to inspect claims for debugging:\n// parts := strings.Split(rawIDToken, \".\")\n// payload, _ := base64.RawURLEncoding.DecodeString(parts[1])\n// fmt.Println(string(payload))","handlingStrategy":"try-catch","validationCode":"// Sync system clock to prevent premature token expiry rejection\n// Ensure NTP is running: sudo timedatectl set-ntp true","typeGuard":null,"tryCatchPattern":"claims, err := provider.Exchange(ctx, code, codeVerifier, nonce)\nif err != nil {\n    if strings.Contains(err.Error(), \"verify OIDC ID token\") {\n        // Decode JWT to inspect claims for debugging\n        parts := strings.Split(rawIDToken, \".\")\n        if len(parts) >= 2 {\n            payload, _ := base64.RawURLEncoding.DecodeString(parts[1])\n            log.Printf(\"ID token payload: %s\", payload)\n            log.Printf(\"Verification failed — check clock skew, audience, issuer, key rotation\")\n        }\n        return\n    }\n}","preventionTips":["Keep the system clock synchronized (NTP/chrony) to avoid clock-skew rejection.","Verify the ClientID matches the token's audience claim.","Restart the kernel if the provider rotated signing keys (refreshes JWKS cache).","Monitor for provider key rotation announcements."],"tags":["oidc","authentication","jwt","verification","clock-skew","runtime"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}