Search code examples
javascriptapipostspotifyes6-promise

Spotify API bad request on api/token authorization Error: 400


I am trying to authorize spotify api requests using Client Credentials Flow on the Spotify API Docs page. Here is my code in javascript ES6 format using the fetch API

    const response = await fetch('https://accounts.spotify.com/api/token', {
    mode: 'no-cors',
    method: 'POST',
    headers: {
      'Authorization': 'Basic Yzg4OWYzMjM5MjI0NGM4MGIyMzIyOTI5ODQ2ZjZmZWQ6MmUzZTM2YTMzMTM5NDM1Mzk3NzM4ZDMxMTg4MzM0Mjc=',
      'Content-type': 'application/x-www-form-urlencoded'
      },
    body: 'grant_type=client_credentials'
});

The console is saying it is a bad request and doesn't return any JSON.

Another thing that really confuses me is that when I send the request using POSTMAN with those headers and that body, it returns exactly what I want (It works) I don't see how this is different from what I'm doing...? Could anyone please help?

Also here is the code from postman in Javascript Jquery Ajax if this helpls:

var settings = {
  "async": true,
  "crossDomain": true,
  "url": "https://accounts.spotify.com/api/token",
  "method": "POST",
  "headers": {
    "Authorization": "Basic Yzg4OWYzMjM5MjI0NGM4MGIyMzIyOTI5ODQ2ZjZmZWQ6MmUzZTM2YTMzMTM5NDM1Mzk3NzM4ZDMxMTg4MzM0Mjc=",
    "Content-Type": "application/x-www-form-urlencoded",
    "Cache-Control": "no-cache",
    "Postman-Token": "2f93918d-2e8e-4fb0-a168-7e153dd83912"
  },
  "data": {
    "grant_type": "client_credentials"
  }
}  

$.ajax(settings).done(function (response) {
  console.log(response);
});

This is what the request looks like in DevTools enter image description here


Solution

  • That particular endpoint is not meant to be consumed client side. You are supposed to use it in some server side script.

    https://developer.spotify.com/documentation/general/guides/authorization-guide/#client-credentials-flow

    The Client Credentials flow is used in server-to-server authentication

    Another hint that it is meant to be server side only is that it uses your client secret as its name implies it is meant to be kept secret and having it viewable on the client isn't very secret.

    So from that endpoint you get the access token which you can then use on the client side to make requests to the other api endpoints like https://api.spotify.com/v1/tracks

    Now as for why it doesn't work in your calls. It works in postman because it ignores CORS, and it properly sends the authorization header. In the browser however you set the fetch() request mode to no-cors. In this mode only certain headers can be sent AND the response back cannot be read by javascript.

    Due to this your request does not send the authorization header as it is not one of the simple headers allowed in no-cors mode. And so your request fails. Even if the authorization went through you wouldn't have been able to read the response anyways as per no-cors rules.

    So if you want to continue using the Client Credentials flow you would:

    1. From the browser, make a request to your own server.

      fetch("http://myserver.com/getToken")
      
    2. On the server you would then do the https://accounts.spotify.com/api/token request from there sending all the correct information. Then send the returned access token back to the client

      //this is assuming a nodejs server environment
      var postQuery = 'grant_type=client_credentials ';
      var request = require('request');
      var express = require('express');
      var app = express();
      app.get('/getToken', function(req, res){
        request({
          url: "https://accounts.spotify.com/api/token",
          method: "POST",
          headers: {
            'Authorization': 'Basic YourBase64EncodedCredentials',
            'Content-Type': 'application/x-www-form-urlencoded',
            'Content-Length': postQuery.length
          },
          body: postQuery
        }, function (error, response, data){
          //send the access token back to client
          res.end(data);
        });    
      });
      
    3. Use that access token in a normal fetch request to the endpoints you need to use as they are setup with the proper CORS headers

      fetch("http://myserver.com/getToken")
      .then(token=>{
        //token will be the token returned from your own server side script
        //now we can make a new request to the tracks (or any other api)
        return fetch("https://api.spotify.com/v1/tracks",{
          headers:{
            'Authorization': `Bearer ${token}`
          }
        }).then(r=>r.json())
      })
      .then(data=>{
         //data will be the data returned from tracks api endpoint
       });