Login failure (possibly code exchange) or redirect failure

So, after reading my stackoverflow posts, auth0 community posts and trying multiple ideas i’m still stumped.

I’ve had an nodejs express app (and rest api) running for many years, and one thing that has never worked (and that i’ve lived with) is that if a user visits a url that i’ve protected with middleware it always redirected back to the root of the site after login rather than the requested url.

i’d like to say i’ve narrowed down the problem to a particular area, but i’m not sure i have;

anyway;
i have all the usual setup in my app.js file:

var strategy = new Auth0Strategy(
      {
        domain: process.env.AUTH0_DOMAIN,
        clientID: process.env.AUTH0_CLIENTID,
        clientSecret: process.env.AUTH0_CLIENT_SECRET,
        callbackURL: process.env.AUTH0_CALLBACK_URL || 'http://127.0.0.1:3000/callback'
      },
      function (accessToken, refreshToken, extraParams, profile, done) {
        // accessToken is the token to call Auth0 API (not needed in the most cases)
        // extraParams.id_token has the JSON Web Token
        // profile has all the information from the user
        return done(null, profile);
      }
    );

    passport.use(strategy);
    
    

    // You can use this section to keep a smaller payload
    passport.serializeUser(function (user, done) {
      done(null, user);
    });

    passport.deserializeUser(function (user, done) {
      done(null, user);
    });

    // config express-session
    var sess = {
      secret: 'ThisisMySecret',
      cookie: {
        httpOnly:false,
        secure:false
      },
      resave: false,
      saveUninitialized: false
    };
    if (app.get('env') === 'prod') {
      console.log("prod environment")
      app.set('trust proxy', 1); // trust first proxy
      sess.cookie.secure = true; // serve secure cookies, requires https
      sess.proxy = true;
      // console.log("session:sess");
      // console.log(sess);
    }  

    
    app.use(session(sess));
    app.use(passport.initialize());
    app.use(passport.session());

a login route:

router.get('/login', function(req, res, next) {
      req.session.returnTo = req.query.returnTo; // Store the returnTo value in session
      console.log("inside login route: " + req.session.returnTo)
      const state = uuidv4(); 
      passport.authenticate('auth0', {
        scope: 'openid email profile',
        state: state // Pass the returnTo value as the state parameter
      })(req, res, next);
    });

a callback route:

router.get('/callback', function(req, res, next) {
      console.log("inside callback route: " + req.session.returnTo)
      passport.authenticate('auth0', function(err, user, info) {
        console.log("inside callback authenticate function: " + req.session.returnTo)
        console.log(user)
        console.log(info)
        if (err) { return next(err); }
        if (!user) {
          console.log(user)
          console.log(info)
          res.render('beta/failed-login', {
            static_path:'/static',
            theme:process.env.THEME || 'flatly',
            pageTitle : "Access Denied",
            pageDescription : "Access Denied",
            query:req.query
          });
        } else {
          req.logIn(user, function (err) {
            if (err) {console.log(err); return next(err); }
            console.log("inside callback route: " + req.session.returnTo)
            console.log("user inside callback route: " + user)
            const returnTo = req.session.returnTo || '/'; // Retrieve the returnTo value from session
            delete req.session.returnTo; // Remove the returnTo value from session
            res.redirect(returnTo);
          });
        }
      })(req, res, next);
    });

some middleware:

 function secured(req, res, next) {
      if (req.isAuthenticated()) {
        return next();
      }
      console.log("query in middleware: " + req.query.state)
      console.log("originalUrl in middleware: " + req.originalUrl)
      const returnTo = req.query.state || req.originalUrl;
      req.session.returnTo = returnTo; // Store the returnTo value in session
      console.log("returnTo in middleware: " + returnTo)
      console.log("session.returnTo in middleware: " + req.session.returnTo)
      res.redirect('/login?returnTo=' + encodeURIComponent(returnTo));
    }

and an example protected route (of many):

 router.get('/user', secured,async function (req, res) {
      const { _raw, _json, userProfile } = req.user;
      console.log(req.user)
      res.render('beta/user', {
        userProfile: JSON.stringify(userProfile, null, 2),
        static_path:'/static',
        theme:process.env.THEME || 'flatly',
        pageTitle : "User Profile",
        pageDescription : "User Profile",
      });
    });

and i get the following in my logs when i visit /user

Server running at http://127.0.0.1:3000/
query in middleware: undefined
originalUrl in middleware: /user
returnTo in middleware: /user
session.returnTo in middleware: /user
inside login route: /user
inside callback route: /user
inside callback authenticate function: /user
false
{ message: 'Unable to verify authorization request state.' }
false
{ message: 'Unable to verify authorization request state.' }

what i have found is that if i remove

state: state // Pass the returnTo value as the state parameter

from the object passed to the passport.authenticate call in the /login route the login works, but now it doesn’t redirect…

so i’m stumped, i get this behaviour on both the local environment and my production environment which is hosted on heroku (not sure if that’s related…) i had hoped it was just a local build problem related to https which i haven’t put effort into configuring locally yet, but my heroku build is setup for https (https://stockport-badminton.co.uk)

any ideas?

perhaps not an answer as such, but a realisation that i was missing something obvious (or perhaps my solution is a security hole…)

as above - the callback route didn’t have the redirect information.

so - i simply wrote a new variable at a global level and wrote to that… which is available to all the routes, and now it works.

i’d be interested in anybody’s opinions as to whether this is a security hole?