Exportify
21st February 2024Exportify is a web application built with React that allows you to easily download track lists of your Spotify playlists in CSV or JSON format.
You can use Exportify to query public Spotify playlists by their shareable URL, but users can also choose to log in with Spotify and retrieve their own private or saved playlists.
The code for Exportify is open-source under the GPL v3 license.
Table of contents
Demo
Public view
The Public view allows public Spotify playlists to be retrieved by their shareable Spotify URL.
A playlist’s URL can be retrieved many ways in the Spotify desktop/mobile apps via the “share” buttons:
If Exportify matches a public playlist, the user can choose to export a list of songs within the playlist to a CSV or JSON file download:
Below is an example of a playlist exported into CSV, imported into a spreadsheet editor:
Below is an example of a playlist exported into JSON:
Private & Saved view
Users can opt to log in with Spotify, which will send them over to the Spotify accounts service for login, and prompts them to accept Exportify’s requested read permissions.
Logging in will provide access to the Private & Saved view, letting the user see a list of all saved playlists within their account, without needing to query each one by its playlist URL:
Background
I built the skeleton of a similar tool in March 2022 for learning and personal use, and it looked like this:
Hideous, I know. Not sure why I rocked the purple so hard there, but obviously it “just spoke to me.”
My original tool had the very unimaginative name of “Spotify Playlist Exporter”, and it was built in vanilla JavaScript as I hadn’t started learning React at that time. I moved on to other learning and left it incomplete, but otherwise it was a great learning experience.
I’ve learned a lot since 2022, so I set out to produce a better version which was more reliable and implemented the React learning I’ve done since then.
The main remaining items I always wanted to tidy up were:
- Security
- Spotify API calls were made directly from the client as I was heavily practicing JS classes, recursive functions, and file processing. It was much easier to interactively play around with this code in the browser while learning.
- Spotify API access tokens were stored in plain-text within local storage for the above client calls to be made. If the app was published, they would be one unhandled XSS attack case away from floating off into the distance.
- I was not utilising the persistent client-server
state
token exchange that the oAuth2.0 protocol provides during user authentication with the Spotify API. This left the auth process wide open to “login CSRF” attacks, where an attacker could inject their own Spotify code into the auth process, providing my app with the attacker’s Spotify permission and not the user’s permission.
- Accessibility
- There were a lot of accessibility learnings and hangups that I hadn’t learned about at the time. Things like
aria-controls
andaria-expanded
, live regions, better screen reader support, definitely were lacking from the original build.
- There were a lot of accessibility learnings and hangups that I hadn’t learned about at the time. Things like
- Design
- My original focus was learning to build out well-designed classes that could handle API operations and process data into text files; the UI was very much an afterthought, and it shows.
I used this tool a lot, and if I wanted to let other people use it, I knew these items would need to be resolved so I could sleep better at night.
Behind the scenes
Frontend
Exportify is single-page application built with React 18 and written in TypeScript.
It uses Tailwind as the CSS framework, and this was the first time I’ve used Tailwind after doing everything in CSS/SASS forever. I think it breathed some life into how I write my own usual CSS and took away a big amount of thought around a million class names.
It also uses the great clsx library for managing className
conditionals within certain components, and the react-hook-form library for the search form.
Static hosting is currently on Netlify, but will be moved to Cloudfront/S3 or Cloudflare Pages in the near future.
Backend
Backend functions are provided by serverless functions, currently leveraging Netlify Functions as a lambda wrapper but being migrated to AWS directly in the near future.
The backend functions proxy Spotify API calls securely and handle user authorisation tasks with the Spotify API.
Exportify’s main data fetching endpoint /api/getPlaylistTracks
automatically pages through Spotify API data to gather a full track list, and will anticipate lambda timeout during API calls to return partial data & the next request URL gracefully to the client for further requests.
DynamoDB is used within the endpoints /api/login
and /api/auth
which handle Spotify API authorisation to facilicate persistent storage & retrieval of an anti-CSRF state
token during the authentication process.
Basic-scoped Spotify access tokens are never exposed to the client during queries. User-scoped access token values are symmetrically encrypted server-side with AES-256, and the cipher is returned to the client as a strict HttpOnly cookie for usage.
The client (or an attacker) cannot exfiltrate a raw Spotify access token from the value of a returned cookie due to this encryption.
Spotify permissions used by Exportify
The only user permission scope that Exportify requests from the Spotify API is the playlist-read-private
permission.
From the Spotify API documentation, this will give Exportify the below permissions to a user’s Spotify account:
- Check if users follow a playlist
- Get a list of current user’s playlists
- Get a list of another user’s playlists
Exportify only uses the “Get a list of current user’s playlists” functionality and does not have permission to access any other personal information about your Spotify account beyond your saved playlists.
No playlist data is stored serverside during the request processes. Only the necessary playlist & track info is processed and returned to the client.
I encourage you to review the backend function code if you would like confirmation on this; a big reason why I open-sourced this code was to ensure people could verify what is being done with the access they grant.
It’s fine if you’re still not comfortable granting these permissions to Exportify, but unfortunately you would be limited to querying public playlists via URL.