无法通过具有客户端凭据流的 CloudFlare Worker 对 Spotify API 进行 OAuth

Posted

技术标签:

【中文标题】无法通过具有客户端凭据流的 CloudFlare Worker 对 Spotify API 进行 OAuth【英文标题】:Cannot OAuth into Spotify API through CloudFlare Worker with Client Credentials Flow 【发布时间】:2021-10-07 14:44:15 【问题描述】:

我正在尝试从 Spotify 获取访问令牌以进一步使用它来访问 Spotify API,而无需将 URL 重定向到 Auth 确认页面。 我有一个 CloudFlare Worker 设置,其中包含此代码,但无论我尝试什么,它都会失败。 根据Client Credential Flow 的文档,我应该向端点发出 POST 请求,如下所示:

const credentials = btoa(`$SPOTIFY_ID:$SPOTIFY_SECRET`);
const auth = await fetch("https://accounts.spotify.com/api/token", 
  method: "POST",
  headers: 
    "Content-type": "application/x-www-form-urlencoded",
    "Authorization": `Basic $credentials`,
  ,
  body: JSON.stringify(
    grant_type: "client_credentials",
  ),
);

在此示例中,SPOTIFY_IDSPOTIFY_SECRET 存储在 Worker 本身的环境变量中,并且是我的 Spotify APP 的有效凭据。 btoa()方法按照格式的要求将凭证转换为Base64,输出正确(测试解码成功)

我从 POST 请求中得到的响应总是一样的:


 "webSocket": null,
 "url": "https://accounts.spotify.com/api/token",
 "redirected": false,
 "ok": false,
 "headers": ,
 "statusText": "Bad Request",
 "status": 400,
 "bodyUsed": false,
    "body": 
      "locked": false
    
 

还测试了使用 curl 运行请求,一切正常,它返回一个有效的令牌。 我在other topic related questions 上看到,由于 OAuth 安全约定,这应该在服务器端运行,但是 CloudFlare 工作人员,即使在 V8 之上运行也应该被视为服务器端,因为它不运行在浏览器中。

我想更好地理解为什么 CloudFlare Workers 环境似乎被认为是客户端,如果我尝试使用 Spotify API Client for Node 在编译时不包含服务器端方法,也会发生这种情况,使它们不存在在已编译的 worker 中(可能是因为 Webpack 捆绑?)

另一个想法是 fetch() 方法默认是客户端的,因此一个可能的解决方案是使用另一个库向 Spotify 发出 POST 请求,但在 Workers 上我不知道可以使用哪个。

我在这里遗漏了什么吗?这花了太多时间来解决,我没有任何提示。

【问题讨论】:

您是否尝试过查看响应正文以查看是否有更详细的错误消息?执行let text = await response.text(),然后查看text。我怀疑它与“客户端”与“服务器端”有什么关系,因为来自 Workers 的请求绝对是服务器端的,Spotify 没有任何理由不这么想。我认为问题实际上可能是您发送的内容类型为“application/x-www-form-urlencoded”,但您发送的实际内容是 JSON。也许您需要发送“application/json”作为类型? @KentonVarda 我认为通过响应您指的是我的代码中的 auth 常量。我在下面发布的 JSON 是 auth 对象及其所有内容的实际字符串化。据我所知,没有其他消息 代码中的auth 变量是一个Response 对象,如下所述:developer.mozilla.org/en-US/docs/Web/API/Response 这不是服务器返回的数据,它是一个封装HTTP 响应的对象。你需要做let text = await auth.text()才能读取服务器返回的实际内容。 【参考方案1】:

我已经设法让它工作,但我不知道如何以及为什么。 基本上通过阅读有关 OAuth 问题的更多信息,我想出了一个绕过方法。 问题似乎是body 参数可能与fetch() 方法发生冲突。

正如 Kenton 所说,我试图寻找更多错误信息,最终我发现了旧代码:


  "error": "unsupported_grant_type",
  "error_description": "grant_type parameter is missing"

在我将 credentials 更改为完全解析为 const 而不是在 headers 对象的 Authorization 键下评估它之前,这没有打印出来。

使其在 CloudFlare Workers 内部工作的方法是 pass the body grant_type param as query string inside the URL,如下所示:

const credentials = `Basic $btoa(`$SPOTIFY_ID:$SPOTIFY_SECRET`)`
const auth = await fetch("https://accounts.spotify.com/api/token?grant_type=client_credentials", 
  method: "POST",
  headers: 
    "Content-type": "application/x-www-form-urlencoded",
    Authorization: credentials,
  ,
);

我现在得到的是与 curl

中使用的令牌相同的正确响应

  "access_token": "randomstringthatrepresentsmytokenifinallysolvedthisshitthanksgodiwasgoinginsane",
  "expires_in": 3600,
  "token_type": "Bearer"

如果有人更清楚为什么这样做有效,而之前的做法不正确,我们鼓励您对此消息发表评论。

【讨论】:

再次,我认为问题在于您发送到服务器的内容类型与内容不匹配。您的请求正文是JSON.stringify( grant_type: "client_credentials" ),即JSON,但您发送的内容类型是“application/x-www-form-urlencoded”,这与JSON不同。您的修复工作有效,因为您完全摆脱了正文,而是在 URL 中传递了 grant_type。但是我认为,如果您使正文类型和内容匹配,那么这也应该可以解决问题。 我明白你的意思,但是由于API Docs 我需要使用application/x-www-form-urlencoded 发送请求,我不知道fetch() 方法是否像这样或者如果Spotify 弄错了 Docs,因为他们精确地发送带有 grant_type: "client_credentials 的 Body 嗯,从我对文档的阅读来看,他们似乎希望正文包含 grant_type=client_credentials(这就是 x-www-form-urlencoded 的样子),而您的代码将发送 @987654336 @ (JSON)。

以上是关于无法通过具有客户端凭据流的 CloudFlare Worker 对 Spotify API 进行 OAuth的主要内容,如果未能解决你的问题,请参考以下文章

未返回带客户端凭据流的范围

Android 上客户端凭据流的访问令牌

Laravel - 具有不同登录凭据的多种用户类型

无法通过 Postman 在 Django oauth 工具包客户端凭据授予中获取访问令牌

关于vmess工具经过cloudflare的dns解析后无法使用

具有 Cloudflare 域的 Ngrok