Skip to content

Instantly share code, notes, and snippets.

@Symbitic
Created May 22, 2021 22:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Symbitic/95c0f4321b310be3a86eb0adf4a268ff to your computer and use it in GitHub Desktop.
Save Symbitic/95c0f4321b310be3a86eb0adf4a268ff to your computer and use it in GitHub Desktop.
Example of obtaining a Spotify token
/*
Make sure to download spotify-token-index.html and rename it to public/index.html
Before running this application, make sure to replace CLIENT_ID and CLIENT_SECRET below.
Also, make sure to register http://localhost:8989/ and http://localhost:8989/callback/ as callbacks in your developer console.
THE TRAILING SLASHES ARE IMPORTANT!
Run this with:
deno run -A spotify-token-app.js
*/
import { Application, Router, send } from 'https://deno.land/x/oak@v7.5.0/mod.ts';
import { getQuery } from 'https://deno.land/x/oak@v7.5.0/helpers.ts';
import querystring from 'https://jspm.dev/querystring@0.2.1';
const CLIENT_ID = '<SPOTIFY_CLIENT_ID>';
const CLIENT_SECRET = '<SPOTIFY_CLIENT_SECRET>';
const PORT = 8989;
const REDIRECT_URI = `http://localhost:${PORT}/callback/`;
function login(ctx) {
const randomBytes = new Uint8Array(16);
crypto.getRandomValues(randomBytes);
const state = randomBytes.join('');
const scope = [
'streaming',
'user-read-email',
'user-read-private',
'user-library-read',
'user-library-modify',
'user-read-playback-state',
'user-modify-playback-state'
];
ctx.response.redirect('https://accounts.spotify.com/authorize?' +
querystring.stringify({
response_type: 'code',
client_id: CLIENT_ID,
scope: scope.join('%20'),
redirect_uri: REDIRECT_URI,
state: state
})
);
}
async function callback(ctx) {
const code = getQuery(ctx).code || null;
const authToken = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
const response = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
headers: {
'Authorization': `Basic ${authToken}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: querystring.stringify({
code: code,
redirect_uri: REDIRECT_URI,
grant_type: 'authorization_code'
})
});
if (response.status !== 200) {
console.log(`Bad response: ${response.statusText}`);
ctx.response.body = JSON.stringify({
'type': 'error',
'error': 'Error while authorizing Spotify'
});
ctx.response.type = 'application/json';
return;
}
const { access_token } = await response.json();
ctx.cookies.set('access_token', access_token);
ctx.response.redirect(`http://localhost:${PORT}/`);
}
async function refresh(ctx) {
const refresh_token = getQuery(ctx).refresh_token;
const authToken = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
const response = await fetch('https://accounts.spotify.com/api/token', {
method: 'POST',
mode: 'cors',
cache: 'no-cache',
headers: {
'Authorization': `Basic ${authToken}`
},
body: JSON.stringify({
grant_type: 'refresh_token',
refresh_token: refresh_token
})
});
const obj = await response.json();
ctx.response.body = JSON.stringify({
'type': 'success',
'data': obj.access_token
});
ctx.response.type = 'application/json';
}
async function test(ctx) {
const accessToken = ctx.cookies.get('access_token');
if (accessToken) {
const response = await fetch('https://api.spotify.com/v1/me', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
if (response.status === 200) {
ctx.response.body = JSON.stringify({
'type': 'success',
'data': accessToken
});
ctx.response.type = 'application/json';
} else {
ctx.response.body = JSON.stringify({
'type': 'error',
'data': 'Invalid or expired token'
});
ctx.response.type = 'application/json';
}
} else {
ctx.response.body = JSON.stringify({
'type': 'error',
'data': 'Invalid token'
});
ctx.response.type = 'application/json';
}
}
const posts = new Router()
.get('/test', test)
.get('/login', login)
.get('/callback', callback)
.get('/refresh_token', refresh);
async function serve(context) {
await send(context, context.request.url.pathname, {
root: `${Deno.cwd()}/public`,
index: 'index.html',
});
}
const app = new Application({
// For signing cookies.
keys: [ 'secret1' ]
});
app.addEventListener('listen', ({ hostname, port }) => {
console.log(`http://${hostname}:${port}/`);
});
app
.use(posts.routes())
.use(serve);
await app.listen({ hostname: 'localhost', port: PORT });
<!--
Make sure to rename this to `public/index.html` and put it in the same folder as spotify-token-app.js
-->
<!DOCTYPE html>
<html>
<head>
<title>Example of obtaining a Spotify token</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<style type="text/css">
#result {
display: none;
}
</style>
</head>
<body>
<div class="container">
<h1>This is an example of the Authorization Code flow</h1>
<a id="login-button" href="/login" class="btn btn-primary">Log in with Spotify</a>
<div id="result"></div>
</div>
<script>
const loginButton = document.getElementById('login-button');
const result = document.getElementById('result');
window.addEventListener('DOMContentLoaded', async () => {
console.log('loaded');
const response = await fetch('/test');
const { type, data } = await response.json();
if (type === 'success') {
loginButton.style.display = 'none';
result.innerHTML = `Token: <code>${data}</code>`;
result.style.display = 'initial';
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment