What have I discovered when building Musicn?

Photo by bruce mars on Unsplash

What have I discovered when building Musicn?

Security (of user data and JWT), and React

Musicn? What's that?

I had this long desire to create a web application that allows people to see what I listen to on Spotify. I have had several attempts at it. Until I made Musicn. Musicn allows a Spotify user to sign up for an account and have their own Spotify profile to share with others. When others click the link, they can see their current listening, top songs of the month, and recently played songs.

Let's talk about Storing data

Relational vs Non-Relational database

I knew I wanted to use a relational database. I was introduced to the concept of a relational database in the previous semester in school, and I wanted to get my feet more wet. Since I'm a student, I wanted to go cheap since I'm broke and have no money 😭.

Choice of database

I settled on Supabase just because it's PostgreSQL, user-friendly because you can insert into the database with just four lines of code without typing any SQL and comes with a table editor in its online dashboard.

I used the official Supabase library for Javascript and realized that database requests take long (around 200+ ms per query). The issue became apparent to me while I was doing Authorization, and I realized that if I have no credentials, it rejects the request in a mere 7ms. However, with credentials, it had to do some queries, resulting in 200+ ms of query time!

I knew something was wrong because the query times for the database should be way shorter than that (based on my school final assignment for the year).

I made a huge realization. The realization is that the Supabase library fetches the Supabase API, which takes around 200+ ms to resolve, and it all started making sense. So instead, I used a library called pg. And my query times are shortened to 90-100+ ms. Sure not a big difference but after several testing, I concluded that the difference it made to the Heroku deployment is 46.8% faster!

Talking about Security?

Security of user data

Security plays a massive role in Musicn since it is hosted publicly for anyone to use. The data that are used and collected are:

Musicn (when Signing up for an account)

  • Email
  • Username
  • Password

Spotify (When linking your Spotify account)

  • Email
  • Refresh Token
  • Display Name
  • Profile Picture URL

It's essential to keep these data locked away and secure. In terms of obtaining user data, I took extra measures such as:

  • Not exposing the Spotify's Refresh Token online (If it is exposed, it allows the attackers to get the token and make a request to get the user's email. This is dangerous, and hence Musicn acts as a Proxy API)
  • Only display crucial information for displaying on the page (This means creating multiple model files to fetch the different sets of data from the database)

cool.drawio.png

How Musicn works in terms of Proxy API to Spotify Web API

Using Supabase Javascript Library old.jpeg

Using pg Library new.jpeg

Note how the fetch request for nabil is way faster (1.18s vs. 558ms) (ignore the top_playing and currently_playing fetches because that uses Musicn as a Proxy API for Spotify Web API)

Security of JWT tokens

Musicn allows users to edit their profile by heading to the Profile page. Users can change their username so they can visit their friend's profile by going to a link such as https://musicnapp.herokuapp.com/user/nabil so hence it is a must to store the JWT token somewhere as part of its front end so that we can send requests to the backend for Authorization to the Profile page.

So I searched online to get some idea on how to store tokens securely because, in my previous assignment, we stored it in LocalStorage, and it just screams SECURITY BREACH to me! For those who don't know, storing JWT or anything remotely "secretive" in the LocalStorage is a big no-no!

After countless hours spent watching excellent talks by developers, I settled on using cookies. At first, it alarmed me because it is stored on the browser, but you can set some parameters to make the cookies way more secure upon further research. You can read this excellent article written by Ryan Glover explaining more about implementing safe cookies.

res.cookie("cookie-name", {
    httpOnly: true,
    secure: true,
    ...options
})

For my explanation, httpOnly cookies do not allow the browser to access the cookie via Javascript code.

Misc Security Stuff

Like what they say, security is obscurity, and that means not letting the attackers know what your application is built on or what database it uses. But Ironically, Musicn is open-sourced, and pretty much the world knows it's built on

  • Express
  • React
  • Tailwind CSS
  • Supabase
  • pg

But if you're building an actual world application, you can remove the x-powered-by header, which tells the attacker what framework you are using, and using the backend as a proxy to APIs make Musicn somewhat more secure (because no keys are exposed)

Using React as a frontend on the same domain/port as the Express app?

One group of developers builds the backend and the frontend separately, but I was dead on making the backend and frontend run on the same domain.

In the building phase, I used fetch in the React application to fetch API requests. It went something along the lines of:

fetch("http://localhost:4000/api/user")

And then it dawned on me. What if I changed the port number? What if I want to change the host? Well, adding a variable may help. Something like:

const host = "http://localhost:4000"
fetch(host + "/api/user")

But that means I have to write the host variable at every component file I created. Another alternative you could do is to export the host variable out of a single Javascript file but it became troublesome every single time I switch between development and production. Do I have to find my way to change the value of host?

That is when I found "Proxying" in React. Since I know that my React app will run on the same host and domain as my backend, I can proxy every fetch request to this host defined in package.json in the React project folder.

     ...
    "proxy": "http://localhost:4000"
} // EOF

From then on, all my fetch requests can look something like this

fetch("/API/user")

So every fetch request in development will pass it through to http://localhost:4000, and later in production, the proxy value is discarded and will pass through the domain.

You can read more about what I've talked about here

In conclusion

Creating Musicn is a wonderful journey. I researched how to make my application way more secure and pleasant to use. Here are some take-aways:

  • Using pg instead of Supabase can give you speed advantages but results may vary (depends on your use case, if you're using Supabase Auth, then yeah, you have to stick with it)
  • Security is obscurity (API only shows data that is needed and does not show everything)
  • Secure Cookies using httpOnly and secure options.
  • Proxying API requests for development in React application.

Did you find this article valuable?

Support Nabil Ridhwan by becoming a sponsor. Any amount is appreciated!