{"record":{"id":"c0d68be921a16b77","repo":"siyuan-note/siyuan","slug":"oauth-token-endpoint-returned-no-access-token","errorCode":null,"errorMessage":"OAuth token endpoint returned no access token","messagePattern":"OAuth token endpoint returned no access token","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/client/oauth.go","lineNumber":687,"sourceCode":"\t}\n\tdefer resp.Body.Close()\n\tbody, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\ttokenErr := &oauthTokenError{}\n\t\tif json.Unmarshal(body, tokenErr) != nil || tokenErr.Code == \"\" {\n\t\t\treturn nil, nil, fmt.Errorf(\"OAuth token endpoint returned %s\", resp.Status)\n\t\t}\n\t\treturn nil, tokenErr, tokenErr\n\t}\n\tresult := &oauthTokenResponse{}\n\tif err = json.Unmarshal(body, result); err != nil {\n\t\treturn nil, nil, err\n\t}\n\tif result.AccessToken == \"\" {\n\t\treturn nil, nil, fmt.Errorf(\"OAuth token endpoint returned no access token\")\n\t}\n\tif result.TokenType != \"\" && !strings.EqualFold(result.TokenType, \"Bearer\") {\n\t\treturn nil, nil, fmt.Errorf(\"OAuth token endpoint returned unsupported token type %q\", result.TokenType)\n\t}\n\treturn result, nil, nil\n}\n\nfunc applyOAuthClientAuthentication(values url.Values, req *http.Request, credential oauthCredential) {\n\tswitch credential.TokenAuthMethod {\n\tcase \"client_secret_basic\":\n\t\tif req != nil {\n\t\t\treq.SetBasicAuth(url.QueryEscape(credential.ClientID), url.QueryEscape(credential.ClientSecret))\n\t\t}\n\tdefault:\n\t\tif values != nil {\n\t\t\tvalues.Set(\"client_id\", credential.ClientID)\n\t\t\tif credential.TokenAuthMethod == \"client_secret_post\" && credential.ClientSecret != \"\" {\n\t\t\t\tvalues.Set(\"client_secret\", credential.ClientSecret)","sourceCodeStart":669,"sourceCodeEnd":705,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/mcp/client/oauth.go#L669-L705","documentation":"The token endpoint returned HTTP 2xx with JSON that decoded successfully, but the access_token field was empty or missing. A protocol-compliant token response must contain a non-empty access_token; an empty one makes the result unusable, so oauthTokenRequest rejects it.","triggerScenarios":"Called from refreshOAuthCredential when the server responds 200 with a JSON body lacking access_token — e.g. a non-standard success body, a server bug, an HTML 'success' page that coincidentally parses differently, or a response containing only refresh-related fields.","commonSituations":"Non-conformant IdP implementations; middleware returning 200 with an error-shaped body; grant type silently ignored (server returns empty token instead of an error); misconfigured token endpoint pointing to a non-token API that returns 200 JSON.","solutions":["Verify the token endpoint URL is the actual token_endpoint from the server's metadata, not another API route.","Dump the 200 response body to inspect what the server actually returned.","Confirm the grant is permitted: some IdPs return 200 with an empty token when refresh tokens are revoked — re-authenticate with a fresh authorize flow.","Upgrade or fix the IdP if it violates RFC 6749 by omitting access_token on success.","Check for proxies rewriting the response body."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"var body map[string]any\njson.NewDecoder(resp.Body).Decode(&body)\nif at, _ := body[\"access_token\"].(string); at == \"\" {\n    // server returns 200 without access_token; it is non-conformant or the endpoint is wrong\n}","typeGuard":null,"tryCatchPattern":"if _, _, err := oauthTokenRequest(ctx, client, credential, values); err != nil {\n    if strings.Contains(err.Error(), \"no access token\") {\n        log.Error(\"non-conformant token response; re-authenticating\")\n        startFreshAuthorizeFlow(server)\n    }\n}","preventionTips":["Confirm the endpoint is the real token_endpoint from metadata, not another API","Log token responses during setup to catch non-conformant IdPs early","Re-authenticate when refresh tokens may have been revoked server-side","Prefer IdPs certified against RFC 6749"],"tags":["oauth","token-endpoint","protocol-violation"],"backgroundTag":"oauth-token-exchange-failed","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"}