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

95,511

Solution 1

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 2

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 3

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

Solution 4

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

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

Share:
95,511

Related videos on Youtube

Alx
Author by

Alx

Updated on October 27, 2020

Comments

  • Alx
    Alx over 3 years

    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>
    
    • Dhrumil Shah
      Dhrumil Shah over 10 years
      Hi I am also doing the same the only difference is when user is successfully log in then i will redirect to welcome.html page with message like'Welcome UserName'. hear UserName will be his/her LoginName. Can you show me how to pass text box value to next page ??
    • Alx
      Alx over 10 years
      not knowing much I'd guess simply pass it through. According from my example that might be: res.render('hidden', {title: 'hidden page', username: 'Tyrion Lannister'});
  • Alx
    Alx over 11 years
    thanks for your reply. Aren't you setting the redirect /account direct in your code? What happens if you have another link let's say /pro_accounts using the same auth.restrict they would be redirected to /account ....
  • chovy
    chovy over 11 years
    That's true. I always redirect to /account after they login, but you could replace it with req.session.redirect_to = req.path
  • Alx
    Alx over 11 years
    hm.. its not quite I'm looking for. I call ensureAuthenticated to figure out if a user is already authed or not, if not it is redirected to /login. From this view I need a way to get back to /account. Calling req.path within /login gives me simple /login back. Using referer doesn't work either plus its not quite certain if the browser sets the referer properly...
  • chovy
    chovy over 11 years
    You need to set req.session.redirect_to = req.path inside your auth middleware. Then read it and delete it in login.post route.
  • linuxdan
    linuxdan over 10 years
    this isn't a great solution for passport which accepts successRedirect as an option at a point that request isn't available
  • Rob Andren
    Rob Andren over 9 years
    Worked on the first pass, but I needed to add the following after the redirect in the passport.authenticate route to avoid being taken back to the returnTo address after subsequent logout/login: req.session.returnTo = null; See this question for additional info about passport's default logout, which, on examination of source, seems to only clear the session.user, and not the entire session.
  • OMGPOP
    OMGPOP almost 9 years
    can i simply pass around a query like ?callback=/profile instead of session
  • linuxdan
    linuxdan almost 9 years
    @OMGPOP you probably could, but that doesn't sound very secure, is there a reason you don't want to take advantage of the session?
  • OMGPOP
    OMGPOP almost 9 years
    or you can use req.flash('redirect')
  • OMGPOP
    OMGPOP almost 9 years
    @linuxdan not at all. but it's just troublesome to extract the redirect link out and put into session, then when user redirect back, assign back the redirect link from the session
  • phillipwei
    phillipwei over 8 years
    @linuxdan why is it insecure?
  • linuxdan
    linuxdan over 8 years
    @phillipwei - if you are passing around authentication related data on the query string, you are giving hackers implementation details of your authentication system.
  • phillipwei
    phillipwei over 8 years
    @linuxdan but the value here is just the url that the user navigated to originally; I don't think that needs securing does it -- or am I missing something obvious?
  • linuxdan
    linuxdan over 8 years
    @phillipwei I'm not a security expert, so I can't say for sure, but when it comes to authentication I try to err on the side of caution. Session variables are generally more secure than url parameters since they aren't passed around in the open where a simple packet sniffer can pick them up. Is the redirect path an asset worth securing? Maybe not, but I could imagine it being a problem when combined with other possible security vulnerabilities.
  • Alexander  Danilov
    Alexander Danilov about 8 years
    BUG: user wants to go to /account but he is not authorized, he is redirected to login and session.redirectTo='/account'. Then he goes to '/' and logins through main page, but session.redirectTo still equals '/account' and he redirects to account but should not
  • mikemaccana
    mikemaccana over 7 years
    This seems like a duplicate of @jaredhanson's answer
  • matthaeus
    matthaeus over 6 years
    Instead of req.path I would use req.originalUrl since it is a combination of baseUrl and path, see the documentation
  • Tom Söderlund
    Tom Söderlund over 6 years
    Can you elaborate? I can see successRedirect being used on the return route, but then the intended destination (i.e. returnTo above) would be lost, right?
  • user752746
    user752746 over 5 years
    There were so many good response here, I was able to use your example and made it work with my project. Thank you!
  • meain
    meain almost 5 years
    need to move the delete line to above the other line, but works otherwise
  • Rich Remer
    Rich Remer about 4 years
    URLs are not passed in the clear if the site is secured with TLS. And if your site isn't secured with TLS, most OAuth implementations aren't secure either, so it's a non-issue. On the other hand, I have not been able to find a way to pass along a callback URL to Google that Google will return when it comes back.
  • Anton Drukh
    Anton Drukh almost 3 years
    Worth adding that express-session needs to be configured so that is saves 'uninitialized' cookies: { saveUninitialized: true }. This means that anonymous users will have sessions created for them, and put more pressure against your session store. On the upside, these sessions will store the returnTo location and be used after an anonymous user authenticates successfully.