0
votes

I'm trying to use the Spotify API and following their instructions on authorization found here: https://github.com/spotify/web-api-auth-examples/blob/master/authorization_code/app.js.

Their version of the Authorization code directly uses routes in the server code, but I wanted to separate the Authorization into its own route. Here is my version of the code:

const authRouter = require("express").Router();
const config = require("../utils/config");
const request = require("request");
const querystring = require("querystring");

// Spotify client configurations
const client_id = config.CLIENT_ID;
const client_secret = config.CLIENT_SECRET;
const redirect_uri = config.REDIRECT_URI;
const stateKey = "spotify_auth_state";

//...

// @route   GET /
// @desc    Prompt User to Login into Spotify
authRouter.get("/", async (req, res) => {
  try {
    var state = generateRandomString(16);
    res.cookie(stateKey, state);

    // Request for user full name, profile image, and email address.
    var scope = "user-read-private user-read-email";

    // 1. Get the user's authorization to access data.
    res.redirect(
      "https://accounts.spotify.com/authorize?" +
        querystring.stringify({
          response_type: "code",
          client_id: client_id,
          scope: scope,
          redirect_uri: redirect_uri,
          state: state,
        })
    );
  } catch {
    console.log("error.");
  }
});

// @route   GET /callback
// @desc    Spotify callback to request access and refresh tokens
authRouter.get("/callback", async (req, res) => {
  try {
    var code = req.query.code || null; // The authorization code returned by the first call.
    var state = req.query.state || null;
    var storedState = req.cookies ? req.cookies[stateKey] : null;

    // Check the state parameter
    if (state === null || state !== storedState) {
      res.redirect(
        "/#" +
          querystring.stringify({
            error: "state_mismatch",
          })
      );
    } else {
      res.clearCookie(stateKey);
      const authOptions = getAuthOptions(
        code,
        redirect_uri,
        client_id,
        client_secret
      );
      // 2. Request an access token and refresh token
      request.post(authOptions, function (error, response, body) {
        if (!error && response.statusCode === 200) {
          // Authorize successful. Access and Refresh Tokens granted.
          var access_token = body.access_token,
            refresh_token = body.refresh_token;

          // Send the tokens to the client so the client can use them in requests to the Spotify API.
          res.redirect(
            "/#" +
              querystring.stringify({
                access_token: access_token,
                refresh_token: refresh_token,
              })
          );
        } else {
          res.redirect(
            "/#" +
              querystring.stringify({
                error: "invalid_token",
              })
          );
        }
      });
    }
  } catch {
    console.log("error.");
  }
});

module.exports = authRouter;

And in my app.js:

const express = require("express");
const authRouter = require("./controllers/auth");
const cors = require("cors");
var cookieParser = require("cookie-parser");

// initialize app with Express to create client to communicate with Spotify
const app = express();

app.use(cors());
app.use(cookieParser());
app.use("/", authRouter);

module.exports = app;

Now when I start my server, my browser returns: "accounts.spotify.com redirected you too many times.". When I tried starting my server in incognito mode, the Spotify login prompt appears. After I enter my credentials, it returns: "accounts.spotify.com redirected you too many times."

I've tried clearing my cookies and caches but that does not work.

In addition, I've confirmed my redirect URI for my server is the same as my redirect URI in my Spotify application's settings.

What can be the reasons the auth seems to be stuck in an infinite loop?

1

1 Answers

0
votes

What's causing the infinite loop is where the code sends the access and refresh tokens back to the client:

          // Send the tokens to the client so the client can use them in requests to the Spotify API.
          res.redirect(
            "/#" +
              querystring.stringify({
                access_token: access_token,
                refresh_token: refresh_token,
              })
          );

Since I have defined the following route:

authRouter.get("/", async (req, res) => {

The access and refresh tokens are redirected to the login page, which will then lead to the callback which redirects to the login again, creating an infinite loop.

How I solved this was to redirect the access and refresh tokens to a different component, not just to "/#" + query.string... as coded in Spotify's example code.

Spotify's example code does not lead to an infinite loop since they defined a /login route for the login page, but I opted my website's root to be the login page since in my case, authenticating should be the first step.