thelinmichael / spotify-web-api-node

A Node.js wrapper for Spotify's Web API.
http://thelinmichael.github.io/spotify-web-api-node/
MIT License
3.11k stars 497 forks source link

Access Token not working #147

Open Ge0rg3 opened 7 years ago

Ge0rg3 commented 7 years ago

Hi,

My access tokens do not seem to be set correctly. My code is taken from the example (and I have relplaced my id & secret):

var SpotifyWebApi = require('spotify-web-api-node');

var clientId = 'myclientid',
    clientSecret = 'myclientsecret';

var spotifyApi = new SpotifyWebApi({
  clientId : clientId,
  clientSecret : clientSecret
});

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']);
    spotifyApi.setAccessToken(data.body['access_token']);
  }, function(err) {
        console.log('Something went wrong when retrieving an access token', err);
  });

I receive the access token and expiration time, but then if I attempt to search for songs/artists/related artists/anything else, I also receive: { [WebapiError: Unauthorized] name: 'WebapiError', message: 'Unauthorized', statusCode: 401 } even though the setAccessToken has been completed.

Furthermore, unlike the other closed issues similar to this, the searches that I am attempting should not require any scopes as they require no user data.

Any help appreciated :)

alexisvisco commented 7 years ago

I got the same error, i didn't know how to fix that shiit.

xabixab commented 7 years ago

I am having the same issue

soundigy commented 7 years ago

Having the same issue. Is there anyone using this module with success?

JoanCherry commented 7 years ago

idem :/

prashanthr commented 7 years ago

Yes. I've had success with this. I use a helper in my app to handle the credential flow.

let spotify = new SpotifyWebApi({
  clientId: 'YOUR_CLIENT_ID'
  clientSecret: 'YOUR_CLIENT_SECRET',
  redirectUri: 'YOUR_REDIRECT_URI'
})

  // Can wrap the following methods in a class for ease of use
  async initialize () {
    const token = await getToken()
    spotify.setAccessToken(token)
  }

  async refreshToken () {
    const token = await getRefreshToken()
    spotify.setAccessToken(token)
  }

  async getToken () {
    const result = await spotify.clientCredentialsGrant()
    return result.body.access_token
  }

  async getRefreshToken () {
    const result = await spotify.refreshAccessToken()
    return result.body.access_token
  }

  async useApi () {
    // initialize or refreshToken as desired
   await spotify.initialize()
    // use api 
    await spotify.search(....)
  }
scottlorimor commented 7 years ago

Having same issue, but python wrapper works fine with the same credentials.

juanlet commented 4 years ago

This is most likely because you didn't set the scopes correctly. This library gives a general 403 error but I went into the package code and verified that the token is set correctly. After that I send a request to get user's albums for example using Curl on the command line to verify the output:

curl -H "Authorization: Bearer yourAccessToken" https://api.spotify.com/v1/me/albums

If you see this

{
  "error" : {
    "status" : 403,
    "message" : "Insufficient client scope"
  }

it means you are not setting the scopes correctly. Check them here

For example, you won't be able to get the albums until you set the "user-library-read" scope:

const spotifyConfig = {
  clientId: "YYYYYY",
  clientSecret: "ZZZZZZZZZZZZZ",
  redirectUri: "http://localhost:3000/api/mycallback",
  scopes: [
    "user-read-private",
    "user-read-email",
    "playlist-modify-public",
    "playlist-read-private",
    "playlist-modify-private",
    "user-read-currently-playing",
    "user-library-read",
    "user-library-modify"
  ]
};

export default spotifyConfig;

Keep trying to add scopes and throwing curl requests until you get a response.

If you want to check that your package version is setting the access token correctly you can go into node_modules/spotify-web-api-node/src/spotify-web-api.js and for example you can check the getMySavedAlbums function

getMySavedAlbums: function(options, callback) {
    return WebApiRequest.builder(this.getAccessToken())
      .withPath('/v1/me/albums')
      .withQueryParameters(options)
      .build()
      .execute(HttpManager.get, callback);
  }

modify it to

getMySavedAlbums: async function(options, callback) {
    await  builtReq = WebApiRequest.builder(this.getAccessToken())
      .withPath('/v1/me/albums')
      .withQueryParameters(options)
      .build()

     console.log(builtReq) 
  },

and run your program again to check if the header is setting the Bearer token correctly so you can rule out any problems with your package version....

Marius-Muis commented 4 years ago

@prashanthr 's async-await approach works when using the client authorization flow. I am still inexperienced at Node.js, but I suspect it's due to Node's non-blocking nature that it'll perform the search query while it's waiting for the clientCredentialsGrant() method to return its response.

Primata commented 3 years ago

I'm still having the same issue! I don't see how the async-await approach helps since functions in this API are using fetch. When I .searchTracks(), I get as response the following data.body: { tracks: { href: 'https://api.spotify.com/v1/search?query=track%3Asexual+artist%3Amarvin+gaye&type=track&offset=0&limit=20', items: [Array], limit: 20, next: 'https://api.spotify.com/v1/search?query=track%3Asexual+artist%3Amarvin+gaye&type=track&offset=20&limit=20', offset: 0, previous: null, total: 33 }

Links give me 401 error, no token provided. Right now I'm a bit lost on which token I'm supposed to have since when I make the call on the data.statusCode I'm getting a 200 and I'm using clientCredentalsGrant() to set the access token, simple client credentials flow.

prashanthr commented 3 years ago

@SnowPrimate It looks like you're getting a valid response after using the client credentials flow. The links you see are API paths but they won't work unless you use the same instance of SpotifyWebApi that you used to make the initial call. Furthermore you will get a 401 if your access_token expires. When you go through the credentials grant, you'll get an access token (short lived) and a refresh token (never expires). Upon getting a 401, you can use the refresh token to get a new access token (via the refreshAccessToken method on the object) and then use that token to make any future requests. The key here for reproducibility is to always use the refresh token flow as shown below (when you get 401s) to ensure you always have a valid access token before making any requests to the Spotify API.

Note(s):

      let swa = new SpotifyWebApi({
        clientId: 'myClientId',
        clientSecret: 'myClientSecret'
        redirectUri: 'http://www.example.com/callback'
        accessToken: 'my-original-access-token-that-probably-expired'
        refreshToken: 'my-refresh-token-after-client-credential-grant-that-i-saved'
      });

      const response = await swa.refreshAccessToken()
      const { access_token, refresh_token } = response.body
      swa.setAccessToken(access_token)
      swa.setRefreshToken(refresh_token) // Optional - also save the refresh token for future use separately
      const searchResultsPageOne = await swa.searchTracks('artist:marvin gaye') // gives you the first page with 20 results as that is the default limit if not specified
      const searchResultsPageTwo = await swa.searchTracks('artist:marvin gaye', { limit: 50, offset: 20 }) // gives you the second page

Docs:

Primata commented 3 years ago

Hey @prashanthr ! really appreaciate your help! I'm still having some issues! My idea is to use the spotifyapi to constantly receive requests of music and automatically put it in a playlist. Do I need to use the refresh token constantly to retrieve a new access token when it expires? Should I build a state which checks for when access_token_expired => refreshAccessToken()? I feel like I'm in a limbo on the refresh token. Would you mind if I PM'd you?

prashanthr commented 3 years ago

@SnowPrimate You're welcome! Yes that is the right idea. To build a completely stateless application that does what you want when you run it or on schedule, you'll need to save your refresh token once and inject it into your app or program on start/whenever it runs. Possible options to store this are:

Here's the general idea

  1. On start, load an access token and refresh token from your preferred storage source (assuming you've done the credentials grant manually once)
  2. Create a SpotifyWebApi instance (using this library) aka let instance = new SpotifyWebApi({...})
  3. Execute a test request to the Spotify API (you can use any simple API call, even a search)
  4. If you get a 401, refresh the token by calling instance.refreshAccessToken() and set the access token on the SpotifyWebApi instance instance.setAccessToken(access_token). Else proceed to next step on a 2xx response.
  5. Do what you need to do by calling APIs to load up your playlist. Remember to batch your requests by limiting to 100 as you can't fetch more than 100 results in a single API call.
  6. Lifecycle end - Your program ends but when it starts up again the access token would have expired, but luckily your refresh token hasn't so it will repeat from step 1 successfully.

Refs: