Redirecting to previous page after authentication in node.js using passport.js

node.jsAuthenticationRedirectExpresspassport.js

node.js Problem Overview


I'm trying to establish a login mechanism using node.js, express and passport.js. The Login itself works quite nice, also sessions are stored nicely with redis but I do have some troubles with redirecting the user to where he started from before being prompted to authenticate.

e.g. User follows link http://localhost:3000/hidden is then redirected to http://localhost:3000/login but then I want him to be redirected again back to http://localhost:3000/hidden.

The purpose of this is, if the user access randomly a page he needs to be logged in first, he shall be redirected to the /login site providing his credentials and then being redirected back to the site he previously tried to access.

Here is my login post

app.post('/login', function (req, res, next) {
    passport.authenticate('local', function (err, user, info) {
        if (err) {
            return next(err)
        } else if (!user) { 
            console.log('message: ' + info.message);
            return res.redirect('/login') 
        } else {
            req.logIn(user, function (err) {
                if (err) {
                    return next(err);
                }
                return next(); // <-? Is this line right?
            });
        }
    })(req, res, next);
});

and here my ensureAuthenticated Method

function ensureAuthenticated (req, res, next) {
  if (req.isAuthenticated()) { 
	  return next();
  }
  res.redirect('/login');
}

which hooks into the /hidden page

app.get('/hidden', ensureAuthenticated, function(req, res){
    res.render('hidden', { title: 'hidden page' });
});

The html output for the login site is quite simple

<form method="post" action="/login">
  
  <div id="username">
    <label>Username:</label>
    <input type="text" value="bob" name="username">
  </div>
  
  <div id="password">
    <label>Password:</label>
    <input type="password" value="secret" name="password">
  </div>

  <div id="info"></div>
    <div id="submit">
    <input type="submit" value="submit">
  </div>

</form>

node.js Solutions


Solution 1 - node.js

In your ensureAuthenticated method save the return url in the session like this:

...
req.session.returnTo = req.originalUrl; 
res.redirect('/login');
...

Then you can update your passport.authenticate route to something like:

app.get('/auth/google/return', passport.authenticate('google'), function(req, res) {
	res.redirect(req.session.returnTo || '/');
    delete req.session.returnTo;
}); 

Solution 2 - node.js

I don't know about passport, but here's how I do it:

I have a middleware I use with app.get('/account', auth.restrict, routes.account) that sets redirectTo in the session...then I redirect to /login

auth.restrict = function(req, res, next){
	if (!req.session.userid) {
		req.session.redirectTo = '/account';
	    res.redirect('/login');
	} else {
		next();
	}
};

Then in routes.login.post I do the following:

var redirectTo = req.session.redirectTo || '/';
delete req.session.redirectTo;
// is authenticated ?
res.redirect(redirectTo);

Solution 3 - node.js

Take a look at connect-ensure-login, which works along side Passport to do exactly what you want!

Solution 4 - node.js

My way of doing things:

const isAuthenticated = (req, res, next) => {
  if (req.isAuthenticated()) {
    return next()
  }
  res.redirect( `/login?origin=${req.originalUrl}` )
};

GET /login controller:

if( req.query.origin )
  req.session.returnTo = req.query.origin
else
  req.session.returnTo = req.header('Referer')

res.render('account/login')

POST /login controller:

  let returnTo = '/'
  if (req.session.returnTo) {
    returnTo = req.session.returnTo
    delete req.session.returnTo
  }

  res.redirect(returnTo);

POST /logout controller (not sure if there is 100% ok, comments are welcome):

req.logout();
res.redirect(req.header('Referer') || '/');
if (req.session.returnTo) {
  delete req.session.returnTo
}

Clear returnTo middleware (clears returnTo from session on any route except auth routes - for me they are /login and /auth/:provider ):

String.prototype.startsWith = function(needle)
{
  return(this.indexOf(needle) == 0)
}

app.use(function(req, res, next) {
  if ( !(req.path == '/login' || req.path.startsWith('/auth/')) && req.session.returnTo) {
    delete req.session.returnTo
  }
  next()
})

This approach have two features:

  • you can protect some routes with isAuthenticated middleware;
  • on any page you can simply click on login URL, and after login return to that page;

Solution 5 - node.js

If you are using connect-ensure-login there is a super-easy, integrated way to do this with Passport using the successReturnToOrRedirect parameter. When used, passport will send you back to the originally requested URL or fallback to the URL you provide.

router.post('/login', passport.authenticate('local', {
  successReturnToOrRedirect: '/user/me',
  failureRedirect: '/user/login',
  failureFlash: true
}));

https://github.com/jaredhanson/connect-ensure-login#log-in-and-return-to

Solution 6 - node.js

@chovy and @linuxdan answers have bug with not clearing session.returnTo if user goes to another page after login redirect (thats doesn't require authentication) and logins through there. So add this code to their implementations:

// clear session.returnTo if user goes to another page after redirect to login
app.use(function(req, res, next) {
    if (req.path != '/login' && req.session.returnTo) {
        delete req.session.returnTo
    }
    next()
})

If you do some ajax requests from login page, you can also exclude them.


Another approach is to use flash in ensureAuthenticated

req.flash('redirectTo', req.path)
res.redirect('/login')

And then in GET login

res.render('login', { redirectTo: req.flash('redirectTo') })

In view add hidden field to login form (example in jade)

if (redirectTo != '')
    input(type="hidden" name="redirectTo" value="#{redirectTo}")

In POST login

res.redirect(req.body.redirectTo || '/')

Notice that redirectTo will clear after first GET login with it.

Solution 7 - node.js

Easiest (and properly) way to achieve this is setting failureRedirect and successRedirect options.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlxView Question on Stackoverflow
Solution 1 - node.jslinuxdanView Answer on Stackoverflow
Solution 2 - node.jschovyView Answer on Stackoverflow
Solution 3 - node.jsJared HansonView Answer on Stackoverflow
Solution 4 - node.jsdeksdenView Answer on Stackoverflow
Solution 5 - node.jsigneosaurView Answer on Stackoverflow
Solution 6 - node.jsAlexander DanilovView Answer on Stackoverflow
Solution 7 - node.jsErayView Answer on Stackoverflow