Need help? Check out Spotify Answers for solutions to a wide range of topics. |
Plan
Premium
Country
Canada
Device
Chrome. Macbook Pro 2013
Operating System
Mac OS X Catalina
My Question or Issue
I am trying to set up my web application to use the Proof Key for Code Exchange. However, when I try try to get the access_token by doing a POST request to https://accounts.spotify.com/api/token, I get the following error
error: "invalid_request"
error_description: "Invalid client secret"
This message does not make sense to me since the PKCE method does not require a client secret. This is what my javascript code looks like. The code is the authorization_code from the redirect callback and the codeVerifier is used for generating the challenge as described in the docs.
const fetchUrl = "https://accounts.spotify.com/api/token";
const fetchBody = {
client_id: process.env.SPOTIFY_CLIENT_ID,
grant_type: "authorization_code",
code: code,
redirect_uri: process.env.BASE_URL + "/spotify-callback",
code_verifier: codeVerifier,
};
const searchParams = Object.keys(fetchBody)
.map((key) => {
return encodeURIComponent(key) + "=" + encodeURIComponent(fetchBody[key]);
})
.join("&");
const response = await fetch(fetchUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: searchParams,
});
Please could I get some advice on what I'm doing wrong? I have read and reread the documentation several times but I cannot figure it out.
More info
For calculating the sha256: https://www.npmjs.com/package/js-sha256
For performing base64Url encoding: https://www.npmjs.com/package/base64url
For random string for code verifier: https://www.npmjs.com/package/crypto-random-string
I just got a proof-of-concept for PKCE running on my local machine. I'm using Axios, but the code's almost identical to yours. I don't think you want the parameters sent in "body". (Yes, I know that the documentation makes it seem that you must do this.)
My Axios call is like this:
const postConfig = {
headers: {
'content-type': 'application/x-www-form-urlencoded',
},
};
return await instance.post(path, qs.stringify(parameters), postConfig)
.catch(() => ({offline: true}));
Notice that the parameters are sent in the path.
My code that calls this looks like this:
const parameters = {
client_id: use.global.clientId,
grant_type: 'authorization_code',
code: local.getItem('code'),
redirect_uri: getRedirectUri() + location.pathname,
code_verifier: local.getItem('codeVerifier'),
};
return api.call('POST', 'https://accounts.spotify.com/api/token', parameters)
.then(response => {
const {access_token, expires_in, refresh_token} = response.data;
if (access_token)
setAccessToken(access_token);
if (expires_in)
setAccessTokenExpiresOn((Math.floor(Date.now() / 1000)) + expires_in);
if (refresh_token)
setRefreshToken(refresh_token);
});
Make sure that in your initial redirect request(step 2.), the code_challenge and code_challenge_method are specified correctly, otherwise the endpoint will assume you are trying to do the "regular" Authorization work flow. This is why it is expecting a client_secret.
In order to try out the PKCE authorization flow I created a little example repository.
Don't hesitate to try it out and compare with your project: