Issue Description
I’m building a music-based web application using HTML/CSS and the Spotify Web API. The authentication process works fine, but I keep running into a 403 error when trying to access user data. The strange thing is that it only works if I manually add the testing account to my app’s user dashboard on the Spotify developer portal.
Error messages I’m seeing:
- “Failed to load resource: the server responded with a status of 403 ()”
- “Error fetching artist data: Check settings on spotify-for-developers, the user may not be registered.”
Code Implementation
main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Music Style Generator</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="welcome-section">
<div class="content-wrapper">
<h1 class="main-heading">
<span>DISCOVER</span>
<span>YOUR</span>
<span>MUSIC</span>
<span>STYLE</span>
</h1>
<p class="description">Generate recommendations based on your listening habits</p>
<a href="https://accounts.spotify.com/authorize?client_id=your_client_id&response_type=code&redirect_uri=your_redirect_uri&scope=user-read-private,user-top-read,user-read-email" class="auth-button">
<img src="spotify-icon.png" alt="Spotify" class="icon">
Authorize with Spotify
</a>
</div>
</div>
<div class="data-section" style="display: none;">
<div class="content-wrapper">
<h2>Your Primary Genre:</h2>
<p class="genre-display">Loading...</p>
<h3>Your Favorite Artist:</h3>
<p class="artist-display"></p>
<img class="artist-photo" src="placeholder.jpg" alt="Artist Photo">
</div>
</div>
<script>
function extractTokenFromUrl() {
const urlHash = window.location.hash.substring(1);
const parameters = new URLSearchParams(urlHash);
return parameters.get("access_token");
}
function parseAccessToken() {
let token = null;
const hashString = window.location.hash.substring(1);
const paramArray = hashString.split('&');
for (let j = 0; j < paramArray.length; j++) {
const keyValue = paramArray[j].split('=');
if (keyValue[0] === 'access_token') {
token = keyValue[1];
break;
}
}
return token;
}
window.addEventListener("load", function() {
const userToken = parseAccessToken();
if (userToken) {
sessionStorage.setItem('spotifyToken', userToken);
window.location.href = 'dashboard.html';
}
});
</script>
</body>
</html>
dashboard.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Music Dashboard</title>
<link rel="stylesheet" href="dashboard.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
const appId = 'your_client_id';
const appSecret = 'your_client_secret';
const callbackUrl = 'your_redirect_uri';
const queryParams = new URLSearchParams(window.location.search);
const authorizationCode = queryParams.get('code');
if (authorizationCode) {
getAccessToken(authorizationCode);
} else {
initiateSpotifyAuth();
}
function initiateSpotifyAuth() {
const permissions = 'user-read-private user-read-email user-top-read user-read-recently-played';
const authEndpoint = `https://accounts.spotify.com/authorize?client_id=${appId}&response_type=code&redirect_uri=${encodeURIComponent(callbackUrl)}&scope=${encodeURIComponent(permissions)}`;
window.location.href = authEndpoint;
}
function getAccessToken(authCode) {
$.ajax({
url: "https://accounts.spotify.com/api/token",
method: "POST",
headers: {
"Authorization": "Basic " + btoa(`${appId}:${appSecret}`)
},
data: {
grant_type: "authorization_code",
code: authCode,
redirect_uri: callbackUrl
},
success: function(data) {
const userToken = data.access_token;
loadUserMusicData(userToken);
},
error: function(xhr, status, error) {
console.error("Token exchange failed:", xhr.responseText || error);
}
});
}
function loadUserMusicData(token) {
$.ajax({
url: "https://api.spotify.com/v1/me/top/artists?time_range=medium_term&limit=1",
method: "GET",
headers: {
"Authorization": "Bearer " + token
},
success: function(data) {
const genreDisplay = $('#primaryGenre');
const artistSection = $('#top-artist-section');
const artistNameDisplay = $('#topArtistName');
const artistPhotoDisplay = $('#topArtistPhoto');
if (data.items.length > 0) {
const favoriteArtist = data.items[0];
const musicGenres = favoriteArtist.genres;
const artistTitle = favoriteArtist.name;
const artistPhoto = favoriteArtist.images.length > 0 ? favoriteArtist.images[0].url : null;
const primaryGenre = musicGenres.length > 0 ? musicGenres[0] : "Genre not found";
genreDisplay.text(primaryGenre);
artistNameDisplay.text(artistTitle);
if (artistPhoto) {
artistPhotoDisplay.attr('src', artistPhoto).show();
} else {
artistPhotoDisplay.hide();
}
artistSection.show();
} else {
genreDisplay.text("No music data found.");
artistSection.hide();
}
},
error: function(xhr, status, error) {
console.error("Failed to load music data:", xhr.responseText || error);
}
});
}
});
</script>
</head>
<body>
<div class="dashboard-page">
<div class="dashboard-wrapper">
<h1 class="primary-genre-heading">Your top music genre is...</h1>
<h2 id="primaryGenre" class="genre-result">Loading...</h2>
<div id="top-artist-section" class="artist-section" style="display: none;">
<h3 class="artist-heading">Your Most Played Artist:</h3>
<p id="topArtistName" class="artist-result"></p>
<img id="topArtistPhoto" alt="Artist" class="artist-photo" style="width: 250px; height: auto; border-radius: 8px;" />
</div>
</div>
</div>
</body>
</html>
I’ve tried expanding the scope permissions to include user-read-playback-state and user-modify-playback-state, but the 403 error persists. The app only works when I manually whitelist user accounts in the developer dashboard. Any ideas on what might be causing this restriction?