Search code examples
javascriptoauth-2.0fetchspotifycloudflare-workers

Cannot OAuth into Spotify API through CloudFlare Worker with Client Credentials Flow


I'm trying to get an Access Token from Spotify to use it further to access the Spotify API without need of redirect URL to an Auth confirm page. I have a CloudFlare Worker setup that has the code for this but it keeps failing no matter what I try. According to the Docs of the Client Credential Flow I'm supposed to make a POST Request to the Endpoint as follows:

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",
  }),
});

In this example SPOTIFY_ID and SPOTIFY_SECRET are stored into the Environment Variables of the Worker itself and are valid credentials of my Spotify APP. The btoa() method converts the credentials into Base64 as requested under the format of <cliend_id:client_secret> and the output is correct (tested decoding it successfully)

The Response I get from the POST Request is always the same:

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

Also tested to run the request with curl and everything works, it returns a valid token. I saw on other topic related questions that this is supposed to be run server side because of the OAuth security convention, but the CloudFlare worker, even though runs on top of V8 should be considered server side as well, since it doesn't run in the browser.

I would like to understand better why it seems that CloudFlare Workers environment is considered client side, this also happens if I try to use the Spotify API Client for Node where it doesn't include the server side methods while compiling, making them none existent in the compiled worker (maybe because of Webpack bundling?)

Another idea is that the fetch() method is client-side by default, so a possible solution would be to use another library to make the POST request to Spotify but on Workers I don't know which one can be used.

Am I missing something here? This has been taking too much time to solve and I have no hint.


Solution

  • I've managed to make it work but I have no clue how and why. Basically by reading more on OAuth questions I figured out a bypass. The problem seems to be the body parameter that might get in conflict with the fetch() method.

    As Kenton mentioned I tried to look for more error info and eventually I came across this with the old code:

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

    This was not printed before I changed the credentials to be fully parsed into the const instead of evaluating it under the Authorization key of the headers object.

    The way to make it work inside CloudFlare Workers is to pass the body grant_type param as query string inside the URL like this:

    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,
      },
    });
    

    What I get now is the same correct response with token as worked in curl

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

    If someone knows better why this works and the previous doesn't it's encouraged to comment to this message.