I'm trying to build an app that allows me to call Spotify's API from a create-react-app client through a nodejs express server. I'm trying to use the Authorization Code Flow.
It works getting the authorization code using the following code to generate the URL, completely on client side (if and how using server-side is another question):
getSpotifyCodeUrl() {
const authEndPoint = 'https://accounts.spotify.com/authorize'
const clientId = CLIENT_ID;
const responseType = 'code';
const redirectUrl = 'http://localhost:3000/';
// TODO: state for cross-site request forgery protection
// cont state = '...';
const scope = 'user-read-private user-read-email';
return(
authEndPoint +
'?response_type=' + responseType +
'&client_id=' + clientId +
(scope ? '&scope=' + encodeURIComponent(scope) : '') +
'&redirect_uri=' + encodeURIComponent(redirectUrl)
)
}
The user simply clicks a link with the href as generated above.
{!this.state.token ? <a className="btn btn--loginApp-link" href={this.getSpotifyCodeUrl()}>
Login to Spotify
</a> : ""}
After the user gets redirected back, I use the following function to extract the authorization code from
componentDidMount() {
this.setState({code: new URLSearchParams(window.location.search).get('code')});
}
With the code I retrieve the access token. Call from client:
getSpotifyAccessToken() {
fetch('/auth?code=' + this.state.code)
.then((res) => res.json())
.then(data => {
this.setState({token: data.token});
localStorage.setItem('token', this.state.token);
});
}
API call on server:
app.get("/auth", (req, res) => {
let code = req.query.code;
let authOptions = {
url: 'https://accounts.spotify.com/api/token',
form: {
code: code,
redirect_uri: 'http://localhost:3000/',
grant_type: 'authorization_code'
},
headers: {
'Authorization': 'Basic ' + (new Buffer.from(clientId + ':' + clientSecret).toString('base64'))
},
json: true
};
request.post(authOptions, function(error, response, body){
if (!error && response.statusCode === 200) {
token = body.access_token;
res.json({ token: "Token: " + body.access_token});
} else {
console.log("/auth response body")
console.log(body)
}
});
});
Strange thing is I get a token, but can also see the following error in my server terminal:
{ error: 'invalid_grant', error_description: 'Invalid authorization code' }
If I then try to use the token to do a (simple) request from client:\
getSpotifyMe() {
fetch('/me?token=' + this.state.token)
.then((res) => res.json())
.then(data => {
console.log(data);
});
}
And corresponding server call:
app.get("/me", (req, res) => {
let token = req.query.token;
console.log("Token: " + token);
let options = {
url: 'https://api.spotify.com/v1/me',
headers: { 'Authorization': 'Bearer ' + token },
json: true
}
request.get(options, function(error, response, body) {
console.log("/me request body");
console.log(body);
if (!error && response.statusCode === 200) {
res.json(body);
} else {
}
})
})
Which gives me a 401 error:
{ error: { status: 401, message: 'Invalid access token' } }
I've tried some things. Doing the call from client, no success. Refreshing tokens, deleting cookies, authorizations from account, but no success. The strange thing is I can use the token said to be invalid in the Spotify Web Console, doing the exact same call I'm trying to do in the application.
Do you know where I'm causing these errors (invalid_grant and 401) in my application? And how I could solve them?