Last active
July 10, 2019 23:52
-
-
Save ktcy/7b131c1126c99dacc3596f54e08bebef to your computer and use it in GitHub Desktop.
Spotify API Authorization
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>Example of the Authorization Code flow with Spotify</title> | |
<style type="text/css"> | |
#login, #loggedin { | |
display: none; | |
} | |
.text-overflow { | |
overflow: hidden; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
width: 500px; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<div id="login"> | |
<h1>This is an example of the Authorization Code flow</h1> | |
<a href="/login">Log in with Spotify</a> | |
</div> | |
<div id="loggedin"> | |
<div id="user-profile"> | |
</div> | |
<div id="oauth"> | |
</div> | |
<button id="obtain-new-token">Obtain new token using the refresh token</button> | |
</div> | |
</div> | |
<script> | |
(function() { | |
function renderUserProfile({ | |
display_name, | |
images, | |
id, | |
email, | |
external_urls, | |
href, | |
country | |
}) { | |
return `<h1>Logged in as ${display_name}</h1> | |
<div> | |
<div><img width="150" src="${images[0].url}"></div> | |
<div> | |
<dl> | |
<dt>Display name</dt><dd>${display_name}</dd> | |
<dt>Id</dt><dd>${id}</dd> | |
<dt>Email</dt><dd>${email}</dd> | |
<dt>Spotify URI</dt><dd><a href="${external_urls.spotify}">${external_urls.spotify}</a></dd> | |
<dt>Link</dt><dd><a href="${href}">${href}</a></dd> | |
<dt>Profile Image</dt><dd><a href="${images[0].url}">${images[0].url}</a></dd> | |
<dt>Country</dt><dd>${country}</dd> | |
</dl> | |
</div> | |
</div>`; | |
} | |
function renderOAuth({access_token, refresh_token}) { | |
return `<h2>OAuth Info</h2> | |
<dl> | |
<dt>Access token</dt><dd class="text-overflow">${access_token}</dd> | |
<dt>Refresh token</dt><dd class="text-overflow">${refresh_token}</dd> | |
</dl>`; | |
} | |
const loginBlock = document.getElementById('login'); | |
const loggedInBlock = document.getElementById('loggedin') | |
const userProfilePlaceholder = document.getElementById('user-profile'); | |
const oauthPlaceholder = document.getElementById('oauth'); | |
const hashParams = new URLSearchParams(window.location.hash.substring(1)); | |
let access_token = hashParams.get('access_token'), | |
refresh_token = hashParams.get('refresh_token'), | |
error = hashParams.get('error'); | |
if (error) { | |
alert('There was an error during the authentication'); | |
} else { | |
if (access_token) { | |
oauthPlaceholder.innerHTML = renderOAuth({access_token, refresh_token}); | |
const headers = { | |
'Authorization': `Bearer ${access_token}` | |
}; | |
fetch('https://api.spotify.com/v1/me', {headers}) | |
.then(response => response.json()) | |
.then(response => { | |
userProfilePlaceholder.innerHTML = renderUserProfile(response); | |
loginBlock.style.display = 'none'; | |
loggedInBlock.style.display = 'unset'; | |
}); | |
} else { | |
loginBlock.style.display = 'unset'; | |
loggedInBlock.style.display = 'none'; | |
} | |
document.getElementById('obtain-new-token').addEventListener('click', () => { | |
const params = new URLSearchParams({refresh_token}); | |
fetch(`/refresh_token?${params.toString()}`) | |
.then(response => response.json()) | |
.then(response => { | |
access_token = response.access_token; | |
oauthPlaceholder.innerHTML = renderOAuth({access_token, refresh_token}); | |
}); | |
}, false); | |
} | |
})(); | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"private": true, | |
"dependencies": { | |
"cookie-parser": "^1.4.3", | |
"cors": "^2.8.4", | |
"express": "^4.16.3", | |
"request": "^2.87.0", | |
"request-promise-native": "^1.0.5" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const express = require('express'); | |
const cors = require('cors'); | |
const spotifyAuth = require('./spotify-auth'); | |
const path = require('path'); | |
const {URL} = require('url'); | |
const client_id = ''; | |
const client_secret = ''; | |
const redirect_uri = 'http://localhost:8888/callback'; | |
const port = new URL(redirect_uri).port; | |
const app = express(); | |
app.use(express.static(path.join(__dirname, 'public'))) | |
.use(cors()) | |
.use(spotifyAuth({client_id, client_secret, redirect_uri})); | |
app.listen(port, () => console.log(`Listening on ${port}`)); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const express = require('express'); | |
const cookieParser = require('cookie-parser'); | |
const request = require('request-promise-native'); | |
const {URL, URLSearchParams} = require('url'); | |
const SPOTIFY_AUTHORIZE_ENDPOINT = 'https://accounts.spotify.com/authorize'; | |
const SPOTIFY_TOKEN_ENDPOINT = 'https://accounts.spotify.com/api/token'; | |
const STATE_KEY = 'spotify_auth_state'; | |
module.exports = function(options = {}) { | |
const {client_id, client_secret, redirect_uri} = options; | |
const credentials = new Buffer(`${client_id}:${client_secret}`).toString('base64'); | |
const requestToken = function(form) { | |
return request.post({ | |
url: SPOTIFY_TOKEN_ENDPOINT, | |
headers: {'Authorization': `Basic ${credentials}`}, | |
json: true, | |
form | |
}); | |
}; | |
const router = express.Router(); | |
router.get('/login', (req, res) => { | |
const url = new URL(SPOTIFY_AUTHORIZE_ENDPOINT); | |
const params = new URLSearchParams(); | |
const state = generateRandomString(16); | |
params.append('client_id', client_id); | |
params.append('redirect_uri', redirect_uri); | |
params.append('response_type', 'code'); | |
params.append('scope', 'user-read-private user-read-email'); | |
params.append('state', state); | |
url.search = params.toString(); | |
res.cookie(STATE_KEY, state); | |
res.redirect(url); | |
}); | |
router.get('/callback', cookieParser(), async (req, res) => { | |
const params = new URLSearchParams(); | |
const {code, state} = req.query; | |
const storedState = req.cookies[STATE_KEY]; | |
if (!storedState || storedState !== state) { | |
params.append('error', 'state_mismatch'); | |
} else { | |
res.clearCookie(STATE_KEY); | |
try { | |
const response = await requestToken({ | |
grant_type: 'authorization_code', code, redirect_uri | |
}); | |
const {access_token, refresh_token} = response; | |
params.append('access_token', access_token); | |
params.append('refresh_token', refresh_token); | |
} catch (error) { | |
params.append('error', 'invalid_token'); | |
} | |
} | |
res.redirect(`/#${params}`); | |
}); | |
router.get('/refresh_token', async (req, res) => { | |
const {refresh_token} = req.query; | |
let access_token; | |
try { | |
const response = await requestToken({grant_type: 'refresh_token', refresh_token}); | |
access_token = response.access_token; | |
} catch (error) { | |
access_token = null; | |
} | |
res.send({access_token}); | |
}); | |
return router; | |
} | |
function generateRandomString(length) { | |
let text = ''; | |
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | |
for (let i = 0; i < length; i++) { | |
text += possible.charAt(Math.floor(Math.random() * possible.length)); | |
} | |
return text; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment