Redirection vers la page précédente après l'authentification dans node.js à l'aide de passport.js

101

J'essaie d'établir un mécanisme de connexion en utilisant node.js, express et passport.js. La connexion elle-même fonctionne assez bien, les sessions sont également bien stockées avec redis mais j'ai quelques problèmes pour rediriger l'utilisateur vers l'endroit où il a commencé avant d'être invité à s'authentifier.

Par exemple, l'utilisateur suit le lien http://localhost:3000/hiddenest ensuite redirigé vers, http://localhost:3000/loginmais je veux qu'il soit redirigé vers http://localhost:3000/hidden.

Le but de ceci est que si l'utilisateur accède de manière aléatoire à une page à laquelle il doit d'abord se connecter, il sera redirigé vers le site / login en fournissant ses informations d'identification, puis redirigé vers le site auquel il a précédemment tenté d'accéder.

Voici mon post de connexion

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);
});

et ici ma méthode ensureAuthenticated

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

qui s'accroche à la /hiddenpage

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

La sortie html pour le site de connexion est assez 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>
Alx
la source
Salut, je fais également la même chose, la seule différence est que lorsque l'utilisateur se connecte avec succès, je vais rediriger vers la page welcome.html avec un message comme «Welcome UserName». entendre UserName sera son LoginName. Pouvez-vous me montrer comment passer la valeur de la zone de texte à la page suivante?
Dhrumil Shah
ne sachant pas grand-chose, je suppose qu'il suffit de passer à travers. D'après mon exemple, cela pourrait être: res.render ('hidden', {title: 'hidden page', username: 'Tyrion Lannister'});
Alx

Réponses:

84

Je ne sais pas pour le passeport, mais voici comment procéder:

J'ai un middleware que j'utilise avec app.get('/account', auth.restrict, routes.account)qui définit redirectTodans la session ... puis je redirige vers / login

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

Ensuite, routes.login.postje fais ce qui suit:

var redirectTo = req.session.redirectTo || '/';
delete req.session.redirectTo;
// is authenticated ?
res.redirect(redirectTo);
chovy
la source
Merci pour votre réponse. Ne définissez-vous pas la redirection / le compte directement dans votre code? Que se passe-t-il si vous avez un autre lien, disons / pro_accounts utilisant le même auth.restrict, ils seraient redirigés vers / account ....
Alx
4
C'est vrai. Je redirige toujours vers / account après leur connexion, mais vous pouvez le remplacer parreq.session.redirect_to = req.path
chovy
1
hm .. ce n'est pas tout à fait que je cherche. J'appelle ensureAuthenticated pour savoir si un utilisateur est déjà authentifié ou non, sinon il est redirigé vers / login. De cette vue, j'ai besoin d'un moyen de revenir à / account. L'appel de req.path dans / login me permet de me reconnecter simplement. L'utilisation de referer ne fonctionne pas non plus et il n'est pas tout à fait certain que le navigateur configure correctement le referer ...
Alx
6
Vous devez définir req.session.redirect_to = req.pathdans votre middleware d'authentification. Ensuite, lisez-le et supprimez-le dans la login.postroute.
chovy
ce n'est pas une excellente solution pour le passeport qui accepte le succèsRedirect comme une option à un moment où la demande n'est pas disponible
linuxdan
111

Dans votre ensureAuthenticatedméthode, enregistrez l'url de retour dans la session comme ceci:

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

Ensuite, vous pouvez mettre à jour votre passeport.authentifier l'itinéraire vers quelque chose comme:

app.get('/auth/google/return', passport.authenticate('google'), function(req, res) {
    res.redirect(req.session.returnTo || '/');
    delete req.session.returnTo;
}); 
linuxdan
la source
7
J'ai travaillé sur le premier passage, mais j'avais besoin d'ajouter ce qui suit après la redirection dans le passeport.authentifier l'itinéraire pour éviter d'être ramené à l'adresse returnTo après la déconnexion / connexion ultérieure: req.session.returnTo = null;Voir cette question pour plus d'informations sur la déconnexion par défaut du passeport, qui, à l'examen de la source, semble effacer uniquement la session.user, et non la session entière.
Rob Andren
puis-je simplement passer une requête comme? callback = / profile au lieu de session
OMGPOP
@OMGPOP vous pourriez probablement, mais cela ne semble pas très sûr, y a-t-il une raison pour laquelle vous ne voulez pas profiter de la session?
linuxdan
@linuxdan pas du tout. mais c'est juste gênant d'extraire le lien de redirection et de le mettre en session, puis lorsque l'utilisateur redirige en arrière, attribuez le lien de redirection de la session
OMGPOP
5
Au lieu de req.pathcela, j'utiliserais req.originalUrlcar c'est une combinaison de baseUrl et de chemin, voir la documentation
matthaeus
7

Ma façon de faire:

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

Contrôleur GET / login :

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

res.render('account/login')

Contrôleur POST / login :

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

  res.redirect(returnTo);

Contrôleur POST / déconnexion (pas sûr s'il y a 100% ok, les commentaires sont les bienvenus):

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

Clear returnTo middleware (efface returnTo de la session sur n'importe quelle route sauf les routes d'authentification - pour moi, il s'agit de / login et / 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()
})

Cette approche a deux caractéristiques :

  • vous pouvez protéger certaines routes avec le middleware isAuthenticated ;
  • sur n'importe quelle page, vous pouvez simplement cliquer sur l'URL de connexion, et après la connexion, revenir à cette page;
Deksden
la source
Il y a eu tellement de bonnes réponses ici, j'ai pu utiliser votre exemple et l'ai fait fonctionner avec mon projet. Je vous remercie!
user752746
5

Si vous utilisez connect-ensure-login, il existe un moyen intégré très simple de le faire avec Passport en utilisant le successReturnToOrRedirectparamètre. Lorsqu'il est utilisé, le passeport vous renverra à l'URL demandée à l'origine ou à l'URL que vous fournissez.

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

ignosaure
la source
Cela semble être un double de la réponse de @ jaredhanson
mikemaccana
1

Les réponses @chovy et @linuxdan ont un bogue avec le non-effacement session.returnTosi l'utilisateur va à une autre page après la redirection de connexion (cela ne nécessite pas d'authentification) et se connecte par là. Ajoutez donc ce code à leurs implémentations:

// 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()
})

Si vous effectuez des requêtes ajax depuis la page de connexion, vous pouvez également les exclure.


Une autre approche consiste à utiliser le flash dansensureAuthenticated

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

Et puis dans GET login

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

Dans la vue, ajoutez un champ caché au formulaire de connexion (exemple en jade)

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

Dans la connexion POST

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

Notez que redirectTo s'effacera après la première connexion GET avec lui.

Alexandre Danilov
la source
0

Le moyen le plus simple (et correctement) d'y parvenir est le réglage failureRedirectet les successRedirectoptions .

Eray
la source
Peux-tu élaborer? Je peux voir successRedirectêtre utilisé sur l'itinéraire de retour, mais la destination prévue (c'est returnTo-à- dire ci - dessus) serait perdue, non?
Tom Söderlund