You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用SpotifyAPI-NET授权时返回'invalid_grant'错误求助

SpotifyAPI-NET SDK 抛出 invalid_grant 异常,但手动请求正常的解决方法

我在AWS Lambda上用C#搭建后端,前端是本地React服务器,通过OAuth流程获取Spotify授权码后,调用后端接口server.com/api/authenticate?code=SPOTIFYCODE换取访问令牌。手动构造请求能成功获取令牌,但使用SpotifyAPI-NET SDK时,一直抛出带有invalid_grant的SpotifyAPI.Web.APIException异常,排查下来可以从以下几个方向解决:

可能的原因及修复步骤

1. RedirectUri 格式不一致

手动请求直接使用字符串RedirectUri,而SDK中通过new Uri(RedirectUri)创建Uri对象,可能存在格式差异(比如末尾是否带斜杠、大小写)。Spotify对redirect_uri的校验严格,必须和OAuth授权发起时的地址完全一致。

  • 核对前端发起授权时的redirect_uri,与后端手动请求、SDK中使用的地址完全匹配(包括斜杠、大小写)
  • 构造Uri时强制指定绝对路径,避免格式自动修正:
    new Uri(RedirectUri, UriKind.Absolute)
    

2. SDK 默认使用 Basic 认证而非表单传参

Spotify支持两种凭证传递方式:表单中携带client_id和client_secret,或者使用Basic Auth请求头。手动请求是把凭证放在表单里,但SpotifyAPI-NET的OAuthClient默认会用Basic Auth方式传递凭证,如果凭证编码有问题或应用配置限制,就会导致校验失败。

  • 强制SDK使用表单传参方式,构造OAuthClient时指定认证类型:
    var httpClient = new HttpClient();
    var oAuthClient = new OAuthClient(httpClient)
    {
        TokenCredentialType = OAuthClient.CredentialType.RequestBody
    };
    var response = await oAuthClient.RequestToken(
        new AuthorizationCodeTokenRequest(
            ClientId,
            ClientSecret,
            code,
            new Uri(RedirectUri))
    );
    

3. 授权码的URL编码问题

前端传递的授权码可能存在URL编码,手动请求时直接使用参数值没问题,但SDK可能重复编码或处理不当,导致传递给Spotify的授权码无效。

  • 先对传入的code做URL解码:
    string decodedCode = HttpUtility.UrlDecode(code);
    var response = await new OAuthClient().RequestToken(
        new AuthorizationCodeTokenRequest(
            ClientId,
            ClientSecret,
            decodedCode,
            new Uri(RedirectUri))
    );
    

4. 升级SDK版本

旧版本的SpotifyAPI-NET可能存在请求构造的bug,建议升级到最新稳定版,避免已知的兼容性问题。


代码对比

手动请求成功代码

// GET api/authenticate
[HttpGet]
public async Task<ActionResult> Authenticate(string code)
{        
    // Exchange the authorization code for an access token
    var parameters = new Dictionary<string, string>
    {
        { "grant_type", "authorization_code" },
        { "code", code },
        { "redirect_uri", RedirectUri },
        { "client_id", ClientId },
        { "client_secret", ClientSecret }
    };

    var content = new FormUrlEncodedContent(parameters);
    var response = await _httpClient.PostAsync(TokenEndpoint, content);
    
    if (!response.IsSuccessStatusCode) return BadRequest("Failed to get access token.");
    
    var responseContent = await response.Content.ReadAsStringAsync();
    var tokenResponse = JsonSerializer.Deserialize<TokenResponse>(responseContent);
    
    var accessToken = tokenResponse?.AccessToken;

    // Create a new cookie with the access token
    if (string.IsNullOrEmpty(accessToken)) return BadRequest("Failed to get access token. Access token is null or empty.");

    var cookieOptions = new CookieOptions
    {
        HttpOnly = true,
        Secure = true, 
        Expires = DateTimeOffset.Now.AddDays(1),
        SameSite = SameSiteMode.None,
        Domain = Request.Host.Value,
        Path = "/api",
    };
    Response.Cookies.Append("access_token", accessToken, cookieOptions);

    return Ok();
}

SDK失败原代码

// GET api/authenticate
[HttpGet]
public async Task<ActionResult> Authenticate(string code)
{    
    var response = await new OAuthClient().RequestToken(
        new AuthorizationCodeTokenRequest(
            ClientId,
            ClientSecret,
            code,
            new Uri(RedirectUri))
    );

    var accessToken = response.AccessToken;

    // Create a new cookie with the access token
    if (string.IsNullOrEmpty(accessToken)) return BadRequest("Failed to get access token. Access token is null or empty.");

    var cookieOptions = new CookieOptions
    {
        HttpOnly = true,
        Secure = true, 
        Expires = DateTimeOffset.Now.AddDays(1),
        SameSite = SameSiteMode.None,
        Domain = Request.Host.Value,
        Path = "/api",
    };
    Response.Cookies.Append("access_token", accessToken, cookieOptions);

    return Ok();
}

内容的提问来源于stack exchange,提问作者Scott

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 00:15:00