Search code examples
authenticationaxiosoauthspotify

Spotify returning 200 on token endpoint, but response data is encoded


I'm working through this tutorial on creating an app that uses the Spotify API. Everything was going great until I got to the callback portion of authenticating using the authentication code flow.

(I do have my callback URL registered in my Spotify app.)

As far as I can tell, my code matches the callback route that this tutorial and others use. Significantly, the http library is axios. Here's the callback method:

app.get("/callback", (req, res) => {
        const code = req.query.code || null;

        const usp = new URLSearchParams({
            code: code,
            redirect_uri: REDIRECT_URI,
            grant_type: "authorization_code",
        });

        axios({
                    method: "post",
                    url: "https://accounts.spotify.com/api/token",
                    data: usp,
                    headers: {
                        "content-type": "application/x-www-form-urlencoded",
                        Authorization: `Basic ${new Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64")}`,
  },
})
  .then(response => {
    console.log(response.status); // logs 200
    console.log(response.data); // logs encoded strings
    if (response.status === 200) {
      res.send(JSON.stringify(response.data))
    } else {
      res.send(response);
    }
  })
  .catch((error) => {
    res.send(error);
  });

Though the response code is 200, here's a sample of what is getting returned in response.data: "\u001f�\b\u0000\u0000\u0000\u0000\u0000\u0000\u0003E�˒�0\u0000Ee�uS\u0015��\u000e�(\b\u0012h\u0005tC%\u0010\u0014T\u001e�����0��^޳:���p\u0014Ѻ\u000e��Is�7�:��\u0015l��ᑰ�g�����\u0"

It looks like it's encoded, but I don't know how (I tried base-64 unencoding) or why it isn't just coming back as regular JSON. This isn't just preventing me logging it to the console - I also can't access the fields I expect there to be in the response body, like access_token. Is there some argument I can pass to axios to say 'this should be json?'

Interestingly, if I use the npm 'request' package instead of axios, and pass the 'json: true' argument to it, I'm getting a valid token that I can print out and view as a regular old string. Below is code that works. But I'd really like to understand why my axios method doesn't.

app.get('/callback', function(req, res) {
        // your application requests refresh and access tokens
        // after checking the state parameter

        const code = req.query.code || null;
        const state = req.query.state || null;
        const storedState = req.cookies ? req.cookies[stateKey] : null;

        res.clearCookie(stateKey);
        const authOptions = {
                url: 'https://accounts.spotify.com/api/token',
                form: {
                    code: code,
                    redirect_uri: REDIRECT_URI,
                    grant_type: 'authorization_code',
                },
                headers: {
                    Authorization: `Basic ${new Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')}`,
                },
                json: true,
    };

  request.post(authOptions, function (error, response, body) {
    if (!error && response.statusCode === 200) {
      const access_token = body.access_token;
      const refresh_token = body.refresh_token;

      var options = {
        url: 'https://api.spotify.com/v1/me',
        headers: { Authorization: 'Bearer ' + access_token },
        json: true,
    };

    // use the access token to access the Spotify Web API
    request.get(options, function(error, response, body) {
        console.log(body);
    });
    // we can also pass the token to the browser to make requests from there
    res.redirect('/#' + querystring.stringify({
        access_token: access_token,
        refresh_token: refresh_token,
    }));
    } else {
      res.redirect(`/#${querystring.stringify({ error: 'invalid_token' })}`);
    }
  });

});


Solution

  • You need to add Accept-Encoding with application/json in axios.post header.

    The default of it is gzip

    headers: {
       "content-type": "application/x-www-form-urlencoded",
       'Accept-Encoding': 'application/json'
       Authorization: `Basic ${new Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64")}`,
      }