Closed fluency03 closed 7 years ago
Seeing how you probably were posting JSON (since you set the content type to that) then that is probably the problem, you are supposed to pass parameters as normal when the content type is application/x-www-form-urlencoded.
@jsimonsson I have changed to application/x-www-form-urlencoded
but I got 400 Bad Request
But what are you posting? If you are posting a JSON then that is wrong.
@jsimonsson I just would like to do the Client Credentials Flow process. I am not posting any other things.
omg, I just miss read authorization code flow with client credentials :) Never mind me! Let me check your issue again.
I'm gonna go ahead and guess that the Authorization header is the culprit. Do you get an error message aswell besides a 400 Bad Request?
@jsimonsson I am doing Client Credentials Flow not Authorization Code Flow. I am currently just trying this using this: https://apigee.com/console/
Well, does the console understand btoa(CLIENT_ID + ':' + CLIENT_SECRET) or have you done the base64 part before and copying it into the header fields section in that console?
@jsimonsson I am actually planning to call Spotify's api using the client: WeChat WeApp, as following:
function getAccessToken() {
wx.request({
url: 'https://accounts.spotify.com/api/token',
method: 'POST',
data: {
grant_type: 'client_credentials'
},
header: {
'Authorization': 'Basic ' + btoa(CLIENT_ID + ':' + CLIENT_SECRET),
'Content-Type': 'application/x-www-form-urlencoded'
},
success: function(res) {
console.log('getAccessToken-S');
console.log(res.data);
that.setData({
token: res.data
})
},
complete: function() {
console.log('getAccessToken-C');
}
});
}
@jsimonsson when using apigee concole, I directly use the base64 version of CLIENT_ID + ':' + CLIENT_SECRET, which I generated from https://www.base64encode.org/
Ok, then I have no idea, I just tried with curl and it works fine for me. Is this code above running in a browser or not? If it is running in a browser then I would advice against having the secret and client ID there, if it is running in for example node.js then I have no idea. You should get an error message when you get 400 BAD REQUEST tho, so if you do, please post it here.
The code is running as javascript in WeChat WeApp's IDE.
The following is what I got from apigee console:
HTTP/1.1 400 Bad Request
Date: Tue, 04 Oct 2016 14:00:03 GMT
Content-Length: 0
Set-Cookie: csrf_token=AQC6XN2Qeutx7xik3bpyMdg7C_r7gQy8VHVczz4GccQZIJGpLNlWTyECmKmG0VLYUCfn7kw_jXBm7OROGg;Version=1;Domain=accounts.spotify.com;Path=/;Secure
Connection: keep-alive
Server: nginx
@fluency03 It's a little tricky to advise without seeing the raw requests you are making. When you get a 400 Bad Request error, you should also get a body containing a message about the problem and it may contain the exact reason for failure.
My advice would be to try the flow without a framework. I've created a gist here that you can try out: https://gist.github.com/asmitter/796e2906a4f2b4fe9b660f8562383412
If that succeeds, then there is likely something up with how the framework is making requests.
Hey @fluency03 - did @asmitter's gist work for you? If not, please feel free to reopen this issue and we'll see if we can help.
I'm currently having this issue. I tried both the Web API and this made by what looks like 2 spotify devs. i've tried locally and on an EC2 instance. I have no luck in either scenario -- same issue "Bad Request". I feel like there HAS to be something more here. I can pull random artist album's just fine, but the issue seems to be your authentication flow. It doesn't seem like an isolated incident either.
IMO posting a gist of the cURL example doesn't help much either when the cURL examples in the official Spotify Web API docs yield same bad results.
Even tried different versions of node and npm just for the hell of it since nothing else is working to make progress on figuring out why this step's failing :(
@hughrawlinson if you dont mind, could you reopen when you get a chance?
@tetreault happy to! Could you please give us some more context as to what you're trying that's not working? It might be that this isn't the same as the original issue.
415 Unsupported Media Type
?spotify-web-api-node
or something else?I've just tried the client credentials flow myself using curl and it seems to be working fine, so this might be an issue with the spotify-web-api-node
wrapper.
@hughrawlinson thanks for reaching back out!
I'm not using the WeChat WeApp - I'm just trying to successfully do the Client Credentials flow so i can grab a playlist i've made under a dev account I'm treating like a bot.
I've followed along the docs here and even the cURL didn't work for me.
With the Node API I was trying the spotifyApi.clientCredentialsGrant()
method of getting a token but I receive the same "Bad Request".
I've never received a 415 Media Type
error, just the 400 Bad Request.
Here's the code - secret is hidden in a .env file, no worries.
'use strict';
import express from 'express';
import path from 'path';
import fs from 'fs';
import http from 'http';
import https from 'https';
import Clarifai from 'clarifai';
import request from 'request';
import SpotifyWebApi from 'spotify-web-api-node';
import config from './config';
import bodyParser from 'body-parser';
import CircularJSON from 'circular-json';
const app = express();
app.use(express.static(path.join(__dirname, '../public')));
app.use(bodyParser.json({limit: '6mb'}));
app.set('view engine', 'ejs');
// Router for navigating us to the site.
app.get('/', (req, res) => {
res.render('pages/index.ejs');
});
// Router for generating the Spotify playlist
app.get('/spotify', (req, res) => {
console.log('test');
const spotifyValues = {
user: config.spotify.user,
clientId: config.spotify.id,
clientSecret: config.spotify.secret,
redirectUri: 'http://XXXXXXX.compute.amazonaws.com/'
};
// This is the Web API attempt that didn't work
/*
// your application requests authorization
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: {
'Authorization': 'Basic ' + (new Buffer(spotifyValues.clientId + ':' + spotifyValues.clientSecret).toString('base64'))
},
form: {
grant_type: 'client_credentials'
},
json: true
};
console.log(authOptions);
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
// use the access token to access the Spotify Web API
var token = body.access_token;
var options = {
url: 'https://api.spotify.com/v1/users/couplesmixtape',
headers: {
'Authorization': 'Bearer ' + token
},
json: true
};
console.log(token, options);
request.get(options, function(error, response, body) {
console.log(body);
});
}
});
*/
let spotifyApi = new SpotifyWebApi({
clientId : config.spotify.id,
clientSecret : config.spotify.secret,
redirectUri: 'http://XXXXXXXX.compute.amazonaws.com/'
});
// Retrieve an access token
spotifyApi.clientCredentialsGrant()
.then(function(data) {
console.log('The access token expires in ' + data.body['expires_in']);
console.log('The access token is ' + data.body['access_token']);
// Save the access token so that it's used in future calls
spotifyApi.setAccessToken(data.body['access_token']);
}, function(err) {
console.log('Something went wrong when retrieving an access token', err.message);
});
if (config.debug) console.log('A call was made to the spotify endpoint');
});
app.listen(config.ports.http);
if (config.debug) console.log(`App is running on ${config.ports.http}.`);
@hughrawlinson The gist from @asmitter worked on my end once I removed the | jq .
part. This means it has to be something malforming my request in a way that Spotify doesn't like. I need to use bodyParser.json (this line here: app.use(bodyParser.json({limit: '6mb'}));
) for a step that transmits big base64 representations to the server via a POST.
What I'm going to try at this rate is doing the spotify step i need inside a lambda script exposed by API Gateway. Not ideal, but if it works in that microservice then its good enough for right now.
@hughrawlinson I tried putting the client auth in a lambda script to see what happens. It doesn't work there either, even though the shell script example posted by @asmitter did for the same client ID and Secret.
context.succeed
spits out null. Really at a loss.
'use strict';
var request = require('request');
exports.handler = function (event, context) {
console.log(event);
var clientId = 'YYYYYYYYYYYYYYYYYYY';
var clientSecret = 'XXXXXXXXXXXXXXXXXXX';
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: {
'Authorization': 'Basic ' + (new Buffer(clientId + ':' + clientSecret).toString('base64'))
},
form: {
grant_type: 'client_credentials'
},
json: true
};
request.post(authOptions, function(error, response, body) {
if (!error && response.statusCode === 200) {
// use the access token to access the Spotify Web API
var token = body.access_token;
var options = {
url: 'https://api.spotify.com/v1/users/couplesmixtape',
headers: {
'Authorization': 'Bearer ' + token
},
json: true
};
request.get(options, function(error, response, body) {
context.succeed(body);
});
}
});
};
@hughrawlinson I ended up getting it working with the node wrapper for the web api. The source of the problem seems extremely random though... So I know i've been all over the place but I finally got the access token being generated properly. If possible - i'd like your feedback on this.
I store my API keys in a .env file. I have a config.js file that pulls in the values from the .env
file via process.env.FIELD_NAME
. Therein lies the issue. If i hard code my API keys when we instantiate a new SpotifyWebApi
it is functional but otherwise all attempts fail.
This is why I suppose it took so many methods till I found this because I've never had any issue reading in values from a .env
file before using the dotenv
module.
I even scanned the .env file for any invisible characters that somehow got introduced thru VIM that were messing things up but there was nothing there.
Furthermore I have Clarifai API keys stored in the .env file and there was zero issue getting those API keys from that file into the instantiation of a new Clarifai App. The issue here was only present with the spotify keys.
I know the keys are correct too because I copied them from the .env file and pasted them into my server.js file - I didn't re-copy them from the Spotify Dev dashboard.
To make matters even more confusing I printed out the keys from .env -> config.js -> server.js.
For the spotify Client ID only there is an extra single quote being added to the end of the spotify Client ID. However, the Client Secret is fine, totally unscathed. I checked all through - no extra single quote anywhere plus again the other API keys in the .env file dont have this issue...
Very curious, I wish I knew why this was happening.
@tetreault is it possible that the Spotify credentials were at the end of your .env file, and weren't followed by a newline? Depending on how you're reading it, this can cause issues sometimes. I'll close this issue as it looks like it's solved, but I'd like to hear back on whether or not this could've been the issue. Hope your Spotify integration is smooth sailing from here on out 😄
@hughrawlinson nope that is the weird part the spotify credentials were in the middle of the .env file. I had API secret at the bottom for the Clarifai API and its ID & Secret weren't corrupted at all. But yeah, this definitely seems like a dotenv module problem. Thanks for being patient!
Any solution for this 400 BAD Request? I am calling web api from ios app.
@sami4064 double check your api keys to make sure theyre what youre expecting them to be. In my case, I was using the env
module to store my keys but the regex for it, for some wild reason, was choking on my spotify key thus it was resulting in a bad request.
If anyone else is having this issue, ensure you are setting the Content-Type to application/x-www-form-urlencoded
sample just using https
`var auth = 'Basic ' + new Buffer(clientID + ':' + clientSecret).toString('base64'); var data = qs.stringify({'grant_type':'client_credentials'});
var options = {
hostname:'accounts.spotify.com',
path:'/api/token',
method: 'POST',
headers:{
'Authorization': auth,
'Accept':'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
}
};
var r = https.request(options, function(res){
console.log(res.statusCode);
res.on('data', function(c){
console.log(JSON.parse(c));
});
});
r.write("grant_type=client_credentials");
r.end();`
I'm seeing this issue also while attempting the Client Credentials flow—I have been unsuccessful in acquiring my auth token and am getting no body response from the 400 error. I get a successful response using the curl
method, but not using JavaScript.
Can anyone help me troubleshoot? I have included all of my code (except that I've redacted my actual client keys in the first constant declaration) and the entire console output, which was running locally in Chrome 60.0.3112.90 (so I'm not exposing my client keys). I have also linked to the same content in a gist so you can match the console line numbers to the code: https://gist.github.com/jsejcksn/8ad111106d9001b60d5bf631df5da986
console.log:
spotify-api-test.js:23 POST https://accounts.spotify.com/api/token 400 (Bad Request)
(anonymous) @ spotify-api-test.js:23
spotify-api-test.js:30 Fetch problem: Error: Network response was not ok.
spotify-api-test.js:
const myClientInfo = {
clientID: '<client_id>',
clientSecret: '<client_secret>'
};
const myAuthHeaders = {
Authorization: `Basic ${btoa(`${myClientInfo.clientID}:${myClientInfo.clientSecret}`)}`, // Won't work for unicode characters, but I think the Client ID is always ASCII
'Content-Type': 'application/x-www-form-urlencoded'
};
const myAuthBody = new FormData();
myAuthBody.append('grant_type', 'client_credentials');
const myAuthInit = {
method: 'POST',
headers: myAuthHeaders,
body: myAuthBody,
mode: 'no-cors'
};
const myAuthRequest = new Request('https://accounts.spotify.com/api/token', myAuthInit);
fetch(myAuthRequest).then(res => {
if (res.ok) {
return res.json();
}
throw new Error('Network response was not ok.');
})
.then(data => console.log(data))
.catch(err => console.log(`Fetch problem: ${err}`));
@jsejcksn Try this, I am using the axios package but this code is working for me:
axios({
url: 'https://accounts.spotify.com/api/token',
method: 'post',
params: {
grant_type: 'client_credentials'
},
headers: {
'Accept':'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
},
auth: {
username: 'YOUR-CLIENT-ID',
password: 'YOUR-CLIENT-SECRET'
}
}).then(function(response) {
console.log(response);
}).catch(function(error) {
});
The issue is that grant_type
must be in the query params, not as a form/json data.
@Agnostic Thanks for taking a look. I tried various methods of supplying the parameters (directly modifying the url, supplying as body content, etc.) in the query component of the url https://accounts.spotify.com/api/token?grant_type=client_credentials
, but the result didn't change—still 400 (Bad Request)
.
Any other ideas?
Try to use my code with axios, if it does not work, then you have a wrong value somewhere https://github.com/mzabriskie/axios
I modified my script to now use a FormData object:
const myClientInfo = { // but using actual client ID and secret values
clientID: '<client_id>',
clientSecret: '<client_secret>'
};
const myAuthUrl = new URL('https://accounts.spotify.com/api/token');
const myAuthHeaders = {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${btoa(`${myClientInfo.clientID}:${myClientInfo.clientSecret}`)}` // Won't work for unicode characters, but I think the Client ID is always ASCII
};
const myAuthQueryParams = {
grant_type: 'client_credentials'
};
let myAuthFormBody = new FormData();
appendObjToFormBody(myAuthQueryParams, myAuthFormBody);
const myAuthInit = {
method: 'POST',
headers: myAuthHeaders,
body: myAuthFormBody,
mode: 'no-cors'
};
const myAuthRequest = new Request(myAuthUrl, myAuthInit);
fetch(myAuthRequest)
.then(res => {
if (res.ok) {
return res.json();
}
throw new Error('Network response was not ok.');
})
.then(data => console.log(data))
.catch(err => console.log(`Fetch problem: ${err}`));
function appendObjToFormBody (obj, form) {
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
form.append(prop, obj[prop]);
}
}
}
but I still get the error 400 (Bad Request)
message in console. However, I inspected the token in Chrome > Dev Tools > Network > Name > Token > Preview and discovered error: "invalid_client"
, which was not present in the error message in the console. It makes me wonder if Spotify has specifically blocked browser requests for the Client Credentials flow.
@Agnostic I could try with axios, but I don't want to add that lib to my code, so it wouldn't help solve the problem at hand.
Hi @jsejcksn, the issue is that the grant_type
param should be in the url, not as a form.
1.- You need to remove this header:
'Content-Type': 'application/x-www-form-urlencoded',
2.- Remove body from myAuthInit
:
body: myAuthFormBody,
3.- Change myAuthUrl
to:
const myAuthUrl = new URL('https://accounts.spotify.com/api/token?grant_type=client_credentials');
I'm not sure if you need to remove mode: 'no-cors'
, but try and tell me if that works :)
@Agnostic I appreciate your effort to help me sort this out—I am very confused about why I can't get this working and I can't seem to find even one vanilla JS browser example of this flow.
I tried what you said. When I commented a couple of days ago, I meant that I tried every possible method of supplying the parameters, including your recommendation. When I try as you recommend, the error is 400 (Bad Request)
with these additional details in Dev Tools: error: "server_error", error_description: "Unexpected status: 400"
.
@jsejcksn A last chance...
Try again with this code:
const myClientInfo = { // but using actual client ID and secret values
clientID: '<client_id>',
clientSecret: '<client_secret>'
};
const myAuthUrl = new URL('https://accounts.spotify.com/api/token?grant_type=client_credentials');
const auth = btoa(`${myClientInfo.clientID}:${myClientInfo.clientSecret}`);
const myAuthHeaders = {
Accept: 'application/json',
Authorization: `Basic ${auth}`
};
const myAuthInit = {
method: 'POST',
headers: myAuthHeaders
};
const myAuthRequest = new Request(myAuthUrl, myAuthInit);
fetch(myAuthRequest)
.then(res => {
if (res.ok) {
return res.json();
}
throw new Error('Network response was not ok.');
})
.then(data => console.log(data))
.catch(err => console.log(`Fetch problem: ${err}`));
@Agnostic Console log still errors 400 (Bad Request)
. Thanks for the effort. I'm just going to raise a new issue.
the answer it's simple:
write the body on this way:
grant_type=client_credentials
Not used {} or ""
Pretty sure the problem is that you cant post using the client credentials flow. I had this problem when i first started using the API
The method makes it possible to authenticate your requests to the Spotify Web API and to obtain a >higher rate limit than you would get without authentication. Note, however that this flow does not >include authorization and therefore cannot be used to access or manage a user’s private data. This >flow is described in RFC-6749.
You can use the authorization code flow server side in node
Old thread, I know. However, I had the same issue and solved it, I believe:
I was setting the content type to 'application/x-www-form-urlencoded', but not actually encoding the string. Once I did both, it worked.
I have encountered the same problem with you, and I solved it. This method was used. Https://stackoverflow.com/questions/55460718/error-400-when-making-post-request-to-spotify-api-with-axios-on-express-js
import querystring from 'querystring';
$axios.post('https://accounts.spotify.com/api/token',querystring.stringify({ grant_type: 'authorization_code', code: route.query.code, redirect_uri: redirect_uri }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64')) }, }).then(res => { console.log(res) }).catch(error => { console.log(error) })
For more browser friendly approach I've came up to this at the end:
axios.post(
'https://accounts.spotify.com/api/token', \
new URLSearchParams({ \
grant_type: "client_credentials",
code: 'AQBxWnU0SB...l_jU',
redirect_uri: 'https://example.com/callback'
}).toString(),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + document.getElementById('base64').value
},
}
).then(function (response) {
console.log(response.data);
});
Beating a dead horse here but here is the pure VanillaJS solution with promises:
function ajax(request, url, headers, data) {
var promise = new Promise((resolve, reject) => {
var xmlHttp = new XMLHttpRequest();
xmlHttp.withCredentials = true;
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
resolve(xmlHttp.responseText);
} else {
reject(xmlHttp.responseText);
}
}
xmlHttp.open(request, url);
for (var key in headers) {
if (headers.hasOwnProperty(key)) {
xmlHttp.setRequestHeader(key, headers[key]);
}
}
xmlHttp.send(data);
});
return promise;
}
function spotifyAuth() {
var headers = {
"Authorization": "Basic " + CLIENT_SECRET_ID_ENCODED,
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded"
}
var body = new URLSearchParams({
"code": code,
"redirect_uri": redirectUri,
"grant_type": "authorization_code"
}).toString();
ajax("POST", "https://accounts.spotify.com/api/token", headers, body)
.then(function(resp) {
console.log(resp);
})
}
According to the Client Credentials Flow part of Spotiy's web api authorization guide, I made the following request:
and
But get the error:
Based on this and this SO questions, I changed the
However, I got
400 Bad Request
.Anyone has other possible solutions?