expressspotifynodejs-serverapi-authorization

How do I allow users to link with Spotify without exposing my Spotify client id & secret on the frontend?


Here's my setup. I have a NodeJS express server providing endpoints and hosting my frontend which I've built using React. I have the Spotify client id & secret stored in a .env file which the frontend references, and therefore publicly exposes.

Currently, for users to link with Spotify, the frontend can redirect to Spotify's authorize page and pass the client id and secret (and a redirect uri) in the url params. They then log in with their Spotify credentials and accept my app's terms. Then Spotify then redirects them to the provided redirect uri, which is just another page of my React app. The user's refresh token is passed as a url param for my frontend to receive. It then sends refresh token to my server using one of my endpoints and I store it in my database under their account.

This works fine, except for the fact that my app's client id and secret are publicly exposed through my frontend. I'm trying to work out a way to allow users to link with Spotify without having the frontend know this information, because if it leaks then people can make calls to Spotify's API on my behalf. But I can't seem to get around the fact that the client's browser needs to at some point have access to something like this.

const url =
  'https://accounts.spotify.com/authorize?' +
  querystring.stringify({
    response_type: 'code',
    client_id: spotify_client_id,
    scope: spotify_scope,
    redirect_uri: spotify_redirect_uri
  })

window.location.href = url

I'm new to web development so there may be something obvious I'm neglecting. If anyone has any ideas, I'm all ears. Thanks in advance!


Solution

  • In this particular scenario, you’ve designed around the entirely wrong OAuth flow for the job. Client credentials-style authentication/authorization is not intended to be used in the manner you describe, for the reasons you describe. Instead, you should be using the offered authorization code with PKCE flow, which provides similar functionality for web apps, etc. without necessitating the exposure of your sensitive authentication secrets.

    Spotify is pretty explicit about this in their documentation (emphasis mine):

    Which OAuth flow should I use?

    Choosing one flow over the rest depends on the application you are building:

    • If you are developing a long-running application (e.g. web app running on the server) in which the user grants permission only once, and the client secret can be safely stored, then the authorization code flow is the recommended choice.

    • In scenarios where storing the client secret is not safe (e.g. desktop, mobile apps or JavaScript web apps running in the browser), you can use the authorization code with PKCE, as it provides protection against attacks where the authorization code may be intercepted.

    • For some applications running on the backend, such as CLIs or daemons, the system authenticates and authorizes the app rather than a user. For these scenarios, Client credentials is the typical choice. This flow does not include user authorization, so only endpoints that do not request user information (e.g. user profile data) can be accessed.

    • The implicit grant has some important downsides: it returns the token in the URL instead of a trusted channel, and does not support refresh token. Thus, we don’t recommend using this flow.

    It may go without saying, but since you’ve already elected to publicly publish your app secret, you should consider it compromised and invalidate it immediately before malicious actors are able to indeed use it to craft abusive API requests.