Search code examples
node.jsjwtspotifywebsecurity

Spotify JWT Flow


Tech stack: NodeJS backend Angular10 front end

I am implementing the Spotify API into my application. I'm having some issues with the flow.

To start off I have no issues authenticating or retrieving a token, but I am having an issue figuring out what to do with it. Assuming we understand the Spotify flow and are on the same page there is an issue presented for client-side rendered apps and that is how to get our JWT into the storage.

The reason this is an issue is because of the flow Spotify constrains us to, it requires a callback where the token is passed in as a query param.

This callback is on my backend NodeJS server, the endpoint is functioning fine but this issue starts here - I cannot pass in my user_id or any other user information it simply returns a JWT at this point the only thing I can do is send a template to the client but it's of a different domain.

Option (1)

Somehow use the Iframe hack to set a storage item in my other tab that's running my front end; I do not like this cause it's hacky and does not feel the right way to handle this.

Option (2)

Perform some kind of hack that maps user ids from the initial request and maps the correct user id to the correct token and then sending it to the client somehow ( database then GET ) I also don't like this it's hacky

What we don't want is to keep the client secret and other credentials on the front end like some examples I have seen, this is fine for personal stuff but not real apps for very obvious reasons.. All client secrets and passwords should be on the backend never sent to the client only their user JWT

So the issue here summarized, How do I return the JWT to the client without doing something hacky? the code below returns this to the client but brings us to option (1) above and obviously returns a template style which is undesirable cause I have a client-side rendered app!; I want something clean and professional, thank you.

router.get('/musicAuthCallback', function (req, res) {
    const spotifyCode: string = req.query.code;
    if (production.ROUTE_LOGGING) {
        routeLogger.info('Spotify endpoint callback hit');
    }
    const _spotifyService = new SpotifyService();
    _spotifyService.tradeSpotifyAuthToken(spotifyCode)
        .then((jwt: any) => {
            if (jwt === false) {
                res.send({ "message": "Unable to get JWT for Spotify", "status": REQUEST_FAILED, "title": "Authentication Error" });
            } else {
                const template = `<h1>  Success! </h1>  <script>
                var main=window.open("http://localhost:4200/");
                main.setItem('spotify_jwt', ${jwt});
              </script>`
                res.send(template)
            }
        })
        .catch((error) => {
            logger.error(error);
            res.send({ "message": "Something went really wrong..", "status": REQUEST_FAILED, "title": "Please Try Again.." });
        });
})

Solution

  • This is silly but kind of illusive until you poke around the docs more.. the docs didnt make it clear to me I could use "state" as a query param.. in this i was able to pass the users ID into here so &state=600 and this is passed into the users callback URL which makes it possible to attach a user to a specific JWT given by Spotify

    https://accounts.spotify.com/authorize?client_id=5fe01282e94241328a84e7c5cc169164&redirect_uri=http:%2F%2Fexample.com%2Fcallback&scope=user-read-private%20user-read-email&response_type=token&**state=123** <- this can be what you want! and can retrieve it in your callback URL sitting on your NodeJS server like this..

    const spotifyCode: string = req.query.code; // jwt const userID: string = req.query.state; // value you passed in