I am trying to make a spotify auth flow in pure javascript, so a user can sign in, and i can then add a new playlist for their account. From the instructions I've read, I use an auth popup that once they sign in, has the access token in the URL. I have a popup right now that the user can auth with, and once they do it will have the access token in the url.
I need to get the url from my popup and save it as a global var, but I'm having trouble figuring out how to do this in javascript.
https://codepen.io/martin-barker/pen/YzPwXaz
My codepen opens a popup with let popup = window.open(
, can I run a function in my popup to detect when the user successfully authenticates and the url changes? In which case I would want to save the url for parsing and close my popup
My javascript code is as follows:
async function spotifyAuth() {
let result = spotifyLogin()
}
//open popup
function spotifyLogin() {
console.log("inside spotifyLogin, opening popup")
let popup = window.open(`https://accounts.spotify.com/authorize?client_id=5a576333cfb1417fbffbfa3931b00478&response_type=token&redirect_uri=https://codepen.io/martin-barker/pen/YzPwXaz&show_dialog=true&scope=playlist-modify-public`, 'Login with Spotify', 'width=800,height=600')
}
//get url from popup and parse access token????
window.spotifyCallback = (payload) => {
console.log("inside window? ") //this line never appears in console
popup.close()
fetch('https://api.spotify.com/v1/me', {
headers: {
'Authorization': `Bearer ${payload}`
}
}).then(response => {
return response.json()
}).then(data => {
// do something with data
})
}
Request Access Token If the user accepted your request, then your app is ready to exchange the authorization code for an Access Token. It can do this by making a POST request to the /api/token endpoint. This field must contain the value "authorization_code" . The authorization code returned from the previous request.
The access token allows you to make requests to the Spotify Web API. To do so, you need to include the following header in your API calls: HEADER PARAMETER. VALUE. Authorization.
The OAuth2 standard defines four grant types (or flows) to request and get an access token. Spotify implements the following ones: Authorization code + PKCE extension. Client credentials.
Access tokens issued from the Spotify account service has a lifetime of one hour. If a longer session is desired Spotify account service supports the OAuth Code grant flow.
Here is how I did it in JavaScript. Global variable like you mentioned:
var access_token = null;
My url looks something like this for example: https://...home.jsp#access_token=BQAXe5JQOV_xZmAukmw6G430lreF......rQByzZMcOIF2q2aszujN0wzV7pIxA4viMbQD6s&token_type=Bearer&expires_in=3600&state=vURQeVAoZqwYm4dC
After Spotify redirects the user to the uri you specified on the dashboard, I parse the url for the hash containing the access token like so:
var hash = window.location.hash.substring(1);
var accessString = hash.indexOf("&");
/* 13 because that bypasses 'access_token' string */
access_token = hash.substring(13, accessString);
console.log("Access Token: " + access_token);
Which the output is:
Access Token: BQAXe5JQOV_xZmAukmw6G430lreF...........rQByzZMcOIF2q2aszujN0wzV7pIxA4viMbQD6s
I save this access token in the sessionStorage just in case the user navigates away from the page and the url does not contain the access_token. I am assuming this is the implicit grant flow since you want to use pure JavaScript. Just make sure to re obtain a access token every hour since they expire.
I can show you how to obtain the token and use it in an example.
I have a button on a .html page that once clicked calls a function called implicitGrantFlow() in a JavaScript file called
Test.js
function implicitGrantFlow() {
/* If access token has been assigned in the past and is not expired, no request required. */
if (sessionStorage.getItem("accessToken") !== null &&
sessionStorage.getItem("tokenTimeStamp") !== null &&
upTokenTime < tokenExpireSec) {
var timeLeft = (tokenExpireSec - upTokenTime);
console.log("Token still valid: " + Math.floor(timeLeft / 60) + " minutes left.");
/* Navigate to the home page. */
$(location).attr('href', "home.jsp");
} else {
console.log("Token expired or never found, getting new token.");
$.ajax({
url: auth_url,
type: 'GET',
contentType: 'application/json',
data: {
client_id: client_id,
redirect_uri: redirect_uri,
scope: scopes,
response_type: response_type_token,
state: state
}
}).done(function callback(response) {
/* Redirect user to home page */
console.log("COULD THIS BE A SUCCESS?");
$(location).attr('href', this.url);
}).fail(function (error) {
/* Since we cannot modify the server, we will always fail. */
console.log("ERROR HAPPENED: " + error.status);
console.log(this.url);
$(location).attr('href', this.url);
});
}
What I am doing is checking if the access_token info I stored in the sessionStorage is null. I used time since Epoch to generate when the token was created and when it ideally should expire. If these parameters are satisfied then I do not make another call.
Else, I make a call to get an access token, which on success will redirect me to my uri as I mentioned in my previous write up (you'll see I have the redirect in the .fail section. This is due to me not having permission on my school server to setup settings to bypass the CORS related issues preventing my call from being successful even though the redirect url I create is fine.).
Then when my whitelist uri gets loaded (which redirects to my home page) I utilize my <body>
tag.
home.jsp
<body onload="getAccessToken()">
Here in my tag I have it call this function once the page loads. This calls the function getAccessTokens().
/**
* The bread and butter to calling the API. This function will be called once the
* user is redirected to the home page on success and without rejecting the terms
* we are demanding. Once through, this function parses the url for the access token
* and then stores it to be used later or when navigating away from the home page.
*/
function getAccessToken() {
access_token = sessionStorage.getItem("accessToken");
if (access_token === null) {
if (window.location.hash) {
console.log('Getting Access Token');
var hash = window.location.hash.substring(1);
var accessString = hash.indexOf("&");
/* 13 because that bypasses 'access_token' string */
access_token = hash.substring(13, accessString);
console.log("Access Token: " + access_token);
/* If first visit or regaining token, store it in session. */
if (typeof(Storage) !== "undefined") {
/* Store the access token */
sessionStorage.setItem("accessToken", access_token); // store token.
/* To see if we need a new token later. */
sessionStorage.setItem("tokenTimeStamp", secondsSinceEpoch);
/* Token expire time */
sessionStorage.setItem("tokenExpireStamp", secondsSinceEpoch + 3600);
console.log("Access Token Time Stamp: "
+ sessionStorage.getItem("tokenTimeStamp")
+ " seconds\nOR: " + dateNowMS + "\nToken expires at: "
+ sessionStorage.getItem("tokenExpireStamp"));
} else {
alert("Your browser does not support web storage...\nPlease try another browser.");
}
} else {
console.log('URL has no hash; no access token');
}
} else if (upTokenTime >= tokenExpireSec) {
console.log("Getting a new acess token...Redirecting");
/* Remove session vars so we dont have to check in implicitGrantFlow */
sessionStorage.clear();
$(location).attr('href', 'index.html'); // Get another access token, redirect back.
} else {
var timeLeft = (tokenExpireSec - upTokenTime);
console.log("Token still valid: " + Math.floor(timeLeft / 60) + " minutes left.");
}
Here I am storing the token in session storage once I obtain the access token from the url. I use the process mentioned in my earlier post but here is the full JavaScript. If it is still unclear after the comments please let me know.
Now that we have our access token obtained and stored we can now make an api call. Here is how I do it (and have been using qQuery, an example of getting a user's top tracks).
Example api call
/**
* Function will get the user's top tracks depending on the limit and offset
* specified in addition to the time_range specified in JSON format.
* @param time_range short/medium/long range the specifies how long ago.
* @param offset Where the indexing of top tracks starts.
* @param limit How many tracks at a time we can fetch (50 max.)
*/
function getUserTopTracks(time_range, offset, limit) {
$.get({
url: 'https://api.spotify.com/v1/me/top/tracks',
headers: {
'Authorization': 'Bearer ' + access_token,
},
data: {
limit: limit, // This is how many tracks to show (50 max @ a time).
offset: offset, // 0 = top of list, increase to get more tracks.
time_range: time_range // short/medium/long_term time ranges.
},
success: function (response) {
/* Get the items from the response (The limit) tracks. */
res = JSON.parse(JSON.stringify(response.items));
/* Get all the track details in the json */
for (i = 0; i < res.length; i++) {
console.log("Track: " + res[i]);
}
},
fail: function () {
console.log("getUserTopTracks(): api call failed!");
}
});
The parameter time_range is specified as "long_term" to get the user's top tracks since the beginning (read more on Spotify's docs for more info) in addition to offset being 0 to start at the beginning and limit being equal to 50 since that is the max fetch per call.
On success I have my response variable 'response' and I then want the root of parsing to start from the 'items' section to make parsing easier (you do not have to do this, you can simply just use response.xxx.items.xxx). I then print to the console the response.
This is the basic things you can do and how you decide to handle the data or store it is up to you. I am not an expert, I only start learning web programming this past semester and a lot of the practices I am doing might be wrong or incorrect.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With