Search code examples
expressauthenticationoauth-2.0jwtspotify

How to authenticate MERN app that integrates with third party service (Spotify) via OAuth?


I am currently building a MERN stack app that allows the user to sign into their Spotify account and save data separate from their Spotify library (i.e. in the app's DB) as well as take actions in their Spotify account through the app.

My main issue has been around Authentication and security, as I am not sure what the best/most secure practices are in this case.

Moreover I would like users to only have to log in to their Spotify account and not have to maintain a separate account specifically for the app.

My current setup is the following:

  • User visits Spotify authorize link and logs in to Spotify account.
  • Spotify redirects the user back to the front end app with a code query param.
  • React code hits the server with the code to request Spotify access/refresh tokens.
  • The server fetches the Spotify access tokens, then hits the spotify get current users profile API
  • The server ensures a user is created and constructs a separate JWT, with similar expire time as the Spotify access token.
  • Finally, the server responds to the client with both the Spotify access tokens for use with the Spotify API as well as the JWT to communicate back with the server.

This seems to work okay but I'm not sure if this goes against best practices when integrating with a separate service via OAuth, especially since I'm maintaining two separate tokens.

I based this approach on this other SA answer, but not sure if I followed it correctly: https://stackoverflow.com/a/48863310/5486699

I also tried using passport-spotify but that seemed to be more for the server to interact with the Spotify API so I don't think it fits my use case.


Solution

  • First:

    This will take you a long way if you can remember this seemingly subtle piece of info.

    OAuth

    • IS an Authorization framework
    • NOT an Authentication framework.

    OAuth gets you an access token which grants you access to some protected resources. Those resources are owned by some user. That token is proof that that user/owner has authorized your app's access to those resources.

    However, OIDC IS an Authentication framework.

    In fact, it was built specifically because OAuth wasn't an Authentication framework. AND, OIDC is built on top of OAuth 2.0.

    If you're app is performing authentication, you'll want to make sure to use OIDC as well (you can use OAuth without OIDC, but you can't use OIDC without OAuth). OIDC introduces a process which piggybacks on the OAuth flow to provide info about that user/owner. Specifically, the ID token and /userinfo endpoint can be used to identify the user, the one that authenticated with the authorization server (in this case Spotify).

    Second:

    The details of your first step are important.

    It's best practice these days to supplement the Authorization Code flow (which you're already using) with PKCE, a later "upgrade" to the original OAuth spec. Intended for public clients (SPAs, native apps) initially, it's useful for certain attacks for private clients (web apps with a server component) as well.

    For confidential clients, the use of PKCE [RFC7636] is RECOMMENDED, as it provides a strong protection against misuse and injection of authorization codes as described in Section 4.5.3.1 and, as a side-effect, prevents CSRF even in presence of strong attackers as described in Section 4.7.1.

    Sec 2.1.1 (and references other relevant sections) Authorization Code Grant Type

    There's a bit there to get into to provide a few lines of code to integrate, but it should give you an idea now of what resources to look for.

    But, for some quick links:

    Spotify's docs on Authz Code with PKCE

    Spotify's how-to guide with step-by-step implementation of Authz Code with PKCE and fetching user info

    Spotify is actually cheating a little here and not strictly using OAuth and OIDC as intended. They use OAuth to get the user's authorization to access... info about that user. That's technically different than knowing the identity of the user that authenticated and authorized access. But, that seems to be the way they do it. OAuth/OIDC are hard and implementations of it will vary, even at some of the largest companies.


    For a deeper dive...

    Confusing OAuth for an authentication framework is common for several reasons, but here are a few that I see the most:

    • People often refer to it, mistakenly, as being used for authentication (as the user in the SO link you provided does)
    • Authentication is required in the process of an OAuth flow
    • OIDC, an Authentication framework, is built on top of OAuth 2.0.

    The second one can be confusing even if you've gone through the spec several times and understand the steps in an OAuth 2.0 flow. The fundamental thing to understand is that with OAuth, your app (client) is asking a user to authorize its access to some protected resources that user owns. In order to do so without just being a huge security gap from the get go, we want to actually get authorization for the actual user/owner of those resources. To do that, the user authenticates at the authorization server. BUT, this is completely outside of OAuth itself. Just sort of a dependency for OAuth. When you get the access token, you don't actually know who the user was that authenticated and gave access. You trust the authorization server did its job.

    For authentication, you want to know who that user that gave authorization is, not just that the real user gave authorization. That's where it can seem like a subtlety, but it a big deal. OIDC was built to solve this problem. OIDC essentially gives a way for your app to know who it was that authorized access not just what access was authorized.