Jammming Project steps 78, 79, and 80 for getAccessToken method - completely lost and confused

I am stuck on the Jammming project, steps #78, 79, and 80 which is creating the getAccessToken method. I have poured over the documentation and all of the instructions but it still isn’t clear to me how to do it.

Below is my code so far for my Spotify.js module, which is incorrect and incomplete, but it just shows where I am stuck.

My current major questions/confusions are:

  1. How is window.location.href related to this problem and how to use it? (the hint suggests using it)
  2. How does it know what user I am requesting authorization from? I did not see that information provided anywhere. client_id is my app’s id per the docs. There is no user id.

In particular, I used the doc page at: Implicit Grant Flow | Spotify for Developers

As you can see, I am very much lost in the sauce. Any help would be greatly appreciated!

let accessToken;

const Spotify = {
  getAccessToken: async () => {
    const client_id = '00f8e8f9bfcb4f05a7ee95c8d6626261';
    const redirect_uri = 'http://localhost:3000';
    const scope = 'playlist-read-collaborative playlist-modify-public playlist-read-private playlist-modify-private';

    let url = 'https://accounts.spotify.com/authorize';
    url += '?response_type=token';
    url += '&client_id=' + encodeURIComponent(client_id);
    url += '&scope=' + encodeURIComponent(scope);
    url += '&redirect_uri=' + encodeURIComponent(redirect_uri);

    let response = await fetch(url, {
      method: 'GET',
    });

    if (response.match(/access_token=([^&]*)/) &&
        response.match(/expires_in=([^&]*)/)) {
      accessToken = response.match(/access_token=([^&]*)/);

    }
  }
};

export default Spotify;

For your first question, in order to get the URL you need to get the access token, you have to assign window.location like this:

window.location = url

From there, you can get the returned URL from window.location.href.
I completed the project, so here’s my code:

getAccessToken() {
    if (accessToken) {
        return accessToken;
    } else {
        // Get access token
        window.location = url;
    }
}

You would also need to assign accessToken at the top of the file like this:

let accessToken = window.location.href.match(/access_token=([^&]*)/) ? window.location.href.match(/access_token=([^&]*)/)[1] : ''

Thanks for the response. That helped a lot. The redirect process in that authorization handshake to get the access token really confused me. I am used to passing request data via urls, but I had never encountered receiving response data via urls as well. It makes a bit more sense to me now.

So I was able to get all the way through the rest of the project, but I am still stuck on that url to get the access token. This is weird because I actually had that part working when I just copy pasted the URL into the browser directly, it gave me the proper redirect for authorization. But now it doesn’t work for some reason. Now, when I paste the URL directly in to test it, it gives me a page with only the error message: “INVALID_CLIENT: Invalid redirect URI”. It seems to suggest something wrong with the redirect URI, but I cannot find any issue, it follows Codecademy’s instructions. I have added the matching information in the application dashboard settings as the redirect URI as well.

I thought maybe it needed the URL encoding by using the encodeURIComponent() method since the Spotify API docs say to use it, and that is what I did last time when it worked I think. But that didn’t help either.

I am also a little suspicious of possible changes to the Spotify API that may no longer align with Codecademy’s instructions. A couple of things I could not confirm in the docs. For example, this line of code:

const headers = { Authorization: `Bearer ${accessToken}` }

That comes from Codecademy’s hint after #86. I have no idea why the token has to be formatted that way with "Bearer " in front of it. I did not see anything regarding that in the API docs.

Also, the Spotify endpoint for creating a new playlist in a user’s account has changed from what Codecademy states. Codecademy’s hint after #94 states it is at: /v1/users/{user_id}/playlists/{playlist_id}/tracks. But per the Spotify API doc at Web API Reference | Spotify for Developers, the endpoint is really at: https://api.spotify.com/v1/playlists/playlist_id/tracks. At least as far as I am understanding it anyway.

Little doubts and uncertainty about several things like that are making it difficult for me to troubleshoot my way out of being stuck as I can’t narrow down issues very well.

I think I am really close, but I am just not sure what else to try to get it to work. My code for my Spotify.js module now looks like the below. It has code for the future steps filled out, but I am just focused on getting the getAccessToken() method to work, rather than worrying about the other methods.

What I currently have now for the getAccessToken() method right now also doesn’t fully make sense to me. In particular, the last conditional if-statement block. If I don’t have the access token already, then I redirect the browser to the constructed URL to make the authorization request. If I am understanding this correctly, that redirect will take the user to a Spotify authorization dialog popup asking the user to authorize the app. Then if the user approves the authorization, they will get redirected back to my app’s page, with their access token and expiration appended to the end of the URL (i.e. a "hash fragment). However, the first time this method is called before the access token is set, it will skip the first 2 conditions and go to the 3rd condition, which will give me the access token, but not actually capture and save it, which is what the 2nd conditional is needed for. And it won’t return it either, which is what the 1st conditional is for. It seems to me almost like the 2nd and 3rd conditional if-statement blocks in the method need another recursive call to getAccessToken() at the bottom of each of them. It also seems to me like getAccessToken() should be an async function to accommodate the delays. But what I have here is how I read the actual instructions.

Clearly, I am missing something here. Any ideas?

Any help is greatly appreciated, thanks.

const CLIENT_ID = '00f8e8f9bfcb4f05a7ee95c8d6626261';
const REDIRECT_URI = 'http://localhost:3000/';

let accessToken = '';

const Spotify = {
  getAccessToken: () => {
    if (accessToken) {
      return accessToken;
    }
    if (window.location.href.match(/access_token=([^&]*)/) &&
        window.location.href.match(/expires_in=([^&]*)/)) {
      accessToken = window.location.href.match(/access_token=([^&]*)/)[1];
      let expiresIn = window.location.href.match(/expires_in=([^&]*)/)[1];
      window.setTimeout(() => accessToken = '', expiresIn * 1000);
      window.history.pushState('Access Token', null, '/');
    }
    if (!accessToken && !window.location.href.match(/access_token=([^&]*)/)) {
      window.location.href = `https://accounts.spotify.com/authorize?client_id=${CLIENT_ID}&response_type=token&scope=playlist-modify-public&redirect_uri=${REDIRECT_URI}`;
    }
  },
  search: async(term) => {
    const headers = { Authorization: `Bearer ${accessToken}` }
    let searchResult = await fetch(`https://api.spotify.com/v1/search?type=track&q=${term}`, { headers: headers });
    searchResult = await searchResult.json();
    searchResult = searchResult.tracks.items;
    return searchResult;
  },
  savePlaylist: async(name, tracks) => {
    if (!name || !tracks) {
      return;
    }
    const currentAccessToken = this.getAccessToken();
    const headers = { Authorization: currentAccessToken };
    let body = { name: name };

    let userID = '';
    let response = await fetch('https://api.spotify.com/v1/me', { headers: headers });
    response = await response.json();
    userID = response.id;

    response = await fetch(`https://api.spotify.com/v1/users/${userID}/playlists`, { method: 'POST', headers: headers, body: body });
    response = await response.json();
    let playlistID = response.id;

    body = { uris: tracks };
    response = await fetch(`https://api.spotify.com/v1/playlists/${playlistID}/tracks`, { method: 'POST', headers: headers, body: body });
    response = await response.json();
    playlistID = response.id;
  }
};

export { Spotify };

You don’t need URL encoding. At least, I didn’t need to use it and the app worked fine. The following code is how I build the authorization URL:

const CLIENT_ID = 'e955ea6edd664e959b9263402c8d29c8';
const REDIRECT_URI = 'http://localhost:3000/';
let baseUrl = 'https://accounts.spotify.com/';

let authorizationUrl = `${baseUrl}authorize`;
authorizationUrl += `?client_id=${CLIENT_ID}`;
authorizationUrl += '&response_type=token';
authorizationUrl += '&scope=playlist-modify-public';
authorizationUrl += `&redirect_uri=${REDIRECT_URI}`;

I don’t think it’s a mistake. The format for the authorization is appropriate for the implementation of OAuth 2.0 bearer tokens, which Spotify uses. A more detailed explanation can be found here if you want to learn more:
authentication - Why is 'Bearer' required before the token in 'Authorization' header in a HTTP request? - Information Security Stack Exchange.


I don’t think
/v1/users/{user_id}/playlists/{playlist_id}/tracks
nor
/v1/playlists/playlist_id/tracks
are wrong. I tested both of those endpoints, and they both achieved the same thing: adding a track to a playlist.


You understood correctly; that is how it works.

For the 2nd condition in the getAccessToken function, you don’t need a recursive call to getAccessToken; you could just add the following line at the end:

return accessToken;

It would be pointless to add that line at the end of the 3rd condition because the user will be redirected to Spotify for authorization. Only after they return to your app with the access token in the URL can they use the functionality of the app. Meaning, they have to call the getAccessToken function twice before they could do anything like saving a playlist with the app. That is a problem I have with my own app as well.

Although, if you want to get the URL for the access token before doing anything else in your app, consider the following:

Update your app so that, as soon as a user visits the app, it redirects the user to Spotify for authorization and retrieves the access token. This is only a suggestion for possible improvement and it is not in Codecademy’s instructions, but you could try if you wish.