Spotify API: Vanilla JavaScript Authorization Code Flow Login Example with PKCE
This is bare-bones code for logging into the Spotify Web API with the PKCE OAuth2 flow. It's bascially straight from the docs but rolled into a single example that also pulls the user id to verify things are working.
Note: The reason this example gets so may scopes is because they are used for the rest of the examples on the site.
Example
JavaScript: Config
const clientId = 'cd3e61bde252419da1e9b1051947a9a4'
const redirectUri = 'http://localhost:3300/pages/2zzlhz3m/'
const verify_token_key = "spotify_example_verify_token"
const access_token_key = "spotify_example_access_token"
const user_id_key = "spotify_example_user_id"
const scopes = [
'user-read-private',
'user-read-email',
'user-top-read',
'user-follow-read',
'user-library-read',
'user-library-modify',
'playlist-modify-public',
'playlist-modify-private',
'playlist-read-private',
'ugc-image-upload',
'playlist-read-collaborative',
'user-read-playback-state',
'user-modify-playback-state',
'user-read-currently-playing',
'user-read-recently-played',
]
const scope = scopes.join(" ")
JavaScript: Main Script
const generateRandomString = (length) => {
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
const values = crypto.getRandomValues(new Uint8Array(length))
return values.reduce((acc, x) => acc + possible[x % possible.length], "")
}
const codeVerifier = generateRandomString(64)
const sha256 = async (plain) => {
const encoder = new TextEncoder()
const data = encoder.encode(plain)
return window.crypto.subtle.digest('SHA-256', data)
}
const base64encode = (input) => {
return btoa(String.fromCharCode(...new Uint8Array(input)))
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_')
}
const startLogin = async () => {
const hashed = await sha256(codeVerifier)
const codeChallenge = base64encode(hashed)
const authUrl = new URL("https://accounts.spotify.com/authorize")
window.localStorage.setItem(verify_token_key, codeVerifier)
const params = {
response_type: 'code',
client_id: clientId,
scope,
code_challenge_method: 'S256',
code_challenge: codeChallenge,
redirect_uri: redirectUri,
}
authUrl.search = new URLSearchParams(params).toString()
window.location.href = authUrl.toString()
}
const getToken = async (code) => {
let codeVerifier = localStorage.getItem(verify_token_key)
const payload = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
client_id: clientId,
grant_type: 'authorization_code',
code,
redirect_uri: redirectUri,
code_verifier: codeVerifier,
}),
}
const url = new URL("https://accounts.spotify.com/api/token")
const body = await fetch(url, payload)
const response = await body.json()
localStorage.setItem(access_token_key, response.access_token)
localStorage.removeItem(verify_token_key)
window.location.href = redirectUri
}
const getData = async (method, endpoint) => {
/****************************************i**
NOTE: This is a sample an only set up to
work with simple GET requests
********************************************/
const payload = {
method: method,
headers: {
'Authorization': `Bearer ${localStorage.getItem(access_token_key)}`
}
}
const body = await fetch(`https://api.spotify.com/v1${endpoint}`, payload)
const response = await body.json()
return response
}
const launchApp = async () => {
while (spotifySpace.children.length > 0) {
spotifySpace.children[0].remove()
}
const logoutButton = document.createElement("button")
logoutButton.innerText = "log out"
logoutButton.addEventListener("click", doLogout)
spotifySpace.appendChild(logoutButton)
const user_data = await getData('GET', '/me')
console.log(user_data)
localStorage.setItem(user_id_key, user_data.id)
spotifyId.innerHTML = user_data.id
}
const doLogout = () => {
localStorage.removeItem(access_token_key)
localStorage.removeItem(user_id_key)
switchToLogin()
}
document.addEventListener('DOMContentLoaded', () => {
if (localStorage.getItem(access_token_key)) {
/* TODO: Check if token has expired */
/* TODO: And deal with refresh tokens */
launchApp()
}
else {
const urlParams = new URLSearchParams(window.location.search);
let code = urlParams.get('code');
/* TODO: Check for error here */
if (code) {
getToken(code)
}
else {
switchToLogin()
}
}
})
const switchToLogin = () => {
while (spotifySpace.children.length > 0) {
spotifySpace.children[0].remove()
}
const loginButton = document.createElement("button")
loginButton.innerText = "log in"
loginButton.addEventListener("click", startLogin)
spotifySpace.appendChild(loginButton)
spotifyId.innerHTML = "Not logged in"
}
~ fin ~