Przekierowanie do poprzedniej strony po uwierzytelnieniu w node.js przy użyciu passport.js

101

Próbuję ustanowić mechanizm logowania przy użyciu node.js, express i passport.js. Samo logowanie działa całkiem nieźle, również sesje są ładnie przechowywane w redis, ale mam pewne problemy z przekierowaniem użytkownika do miejsca, z którego zaczął, zanim zostanie poproszony o uwierzytelnienie.

np. użytkownik podąża za odsyłaczem, http://localhost:3000/hiddena następnie przekierowywany jest na adres, http://localhost:3000/loginale chcę, aby został ponownie przekierowany z powrotem http://localhost:3000/hidden.

Ma to na celu, jeśli użytkownik uzyskuje losowy dostęp do strony, na którą musi być najpierw zalogowany, zostanie przekierowany do witryny / login, podając swoje dane uwierzytelniające, a następnie z powrotem do witryny, do której wcześniej próbował uzyskać dostęp.

Oto mój wpis logowania

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

i tutaj moja metoda zapewniająca uwierzytelnienie

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

który jest zaczepiany na /hiddenstronie

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

Dane wyjściowe html dla witryny logowania są dość proste

<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
źródło
Cześć, robię to samo, jedyna różnica polega na tym, że kiedy użytkownik się zaloguje, przekieruje mnie na stronę welcome.html z komunikatem typu „Welcome UserName”. usłyszysz, że nazwa użytkownika będzie jego nazwą logowania. Czy możesz mi pokazać, jak przekazać wartość pola tekstowego do następnej strony?
Dhrumil Shah
nie wiedząc zbyt wiele, zgaduję, że po prostu to przepuszczę. Według mojego przykładu, może to być: res.render („ukryta”, {tytuł: „ukryta strona”, nazwa użytkownika: „Tyrion Lannister”});
Alx

Odpowiedzi:

84

Nie wiem o paszporcie, ale tak to robię:

Mam wykorzystania middleware I z app.get('/account', auth.restrict, routes.account)tym, że zestawy redirectTow sesji ... wtedy przekierowanie do / login

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

Następnie routes.login.postwykonuję następujące czynności:

var redirectTo = req.session.redirectTo || '/';
delete req.session.redirectTo;
// is authenticated ?
res.redirect(redirectTo);
chovy
źródło
dzięki za odpowiedź. Czy nie ustawiasz przekierowania / konta bezpośrednio w swoim kodzie? Co się stanie, jeśli masz inny link, powiedzmy / pro_accounts używający tego samego auth.restrict, do którego zostaną przekierowani na / account ....
Alx
4
To prawda. Zawsze przekierowuję na / konto po zalogowaniu, ale możesz go zastąpićreq.session.redirect_to = req.path
chovy
1
hm .. to nie do końca szukam. Wywołuję secureAuthenticated, aby dowiedzieć się, czy użytkownik jest już uwierzytelniony, czy nie, a jeśli nie, jest przekierowywany do / login. Z tego widoku potrzebuję sposobu, aby wrócić do / account. Wywołanie req.path w / login daje mi prosty / login z powrotem. Korzystanie z referer również nie działa, a ponadto nie jest do końca pewne, czy przeglądarka ustawia arbitra prawidłowo ...
Alx
6
Musisz ustawić req.session.redirect_to = req.pathwewnątrz swojego oprogramowania pośredniego uwierzytelniania. Następnie przeczytaj go i usuń w login.posttrasie.
chovy
to nie jest świetne rozwiązanie dla paszportu, który akceptuje SuccessRedirect jako opcję w momencie, gdy żądanie nie jest dostępne
linuxdan
111

W swojej ensureAuthenticatedmetodzie zapisz zwrotny adres URL w sesji w następujący sposób:

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

Następnie możesz zaktualizować swój paszport. Uwierzytelnić trasę na coś takiego:

app.get('/auth/google/return', passport.authenticate('google'), function(req, res) {
    res.redirect(req.session.returnTo || '/');
    delete req.session.returnTo;
}); 
linuxdan
źródło
7
Pracowałem na pierwszym przejściu, ale musiałem dodać następujące po przekierowaniu w paszporcie. Uwierzytelnij trasę, aby uniknąć powrotu na adres powrotny po kolejnym wylogowaniu / zalogowaniu: req.session.returnTo = null;Zobacz to pytanie, aby uzyskać dodatkowe informacje o domyślnym wylogowaniu z paszportu, które, po zbadaniu źródła wydaje się czyścić tylko sesję użytkownika, a nie całą sesję.
Rob Andren
czy mogę po prostu przekazać zapytanie, takie jak? callback = / profile zamiast sesji
OMGPOP
@OMGPOP prawdopodobnie byś mógł, ale to nie brzmi zbyt bezpiecznie, czy jest powód, dla którego nie chcesz korzystać z sesji?
linuxdan
@linuxdan wcale. ale to kłopotliwe, aby wyodrębnić łącza przekierowanie i umieścić w sesji, a następnie, gdy użytkownik przekierowanie z powrotem, z powrotem przypisać link przekierowanie z sesji
OMGPOP
5
Zamiast req.pathchciałbym użyć req.originalUrl, ponieważ jest połączeniem baseURL i ścieżki, patrz dokumentacja
Matthaeus
7

Mój sposób robienia rzeczy:

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

GET / login kontroler:

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

res.render('account/login')

Kontroler POST / logowania :

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

  res.redirect(returnTo);

Kontroler POST / wylogowania (nie jestem pewien, czy jest 100% ok, komentarze są mile widziane):

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

Clear returnTo middleware (czyści returnTo z sesji na dowolnej trasie z wyjątkiem tras autoryzacji - dla mnie są to / login i / 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()
})

To podejście ma dwie cechy :

  • niektóre trasy można zabezpieczyć za pomocą oprogramowania pośredniczącego isAuthenticated ;
  • na dowolnej stronie możesz po prostu kliknąć adres URL logowania, a po zalogowaniu powrócić do tej strony;
deksden
źródło
Było tu tak wiele dobrych odpowiedzi, że mogłem wykorzystać twój przykład i sprawić, że zadziała w moim projekcie. Dziękuję Ci!
user752746
5

Jeśli korzystasz z connect-sure-login, istnieje bardzo łatwy, zintegrowany sposób na zrobienie tego z Passport przy użyciu successReturnToOrRedirectparametru. W przypadku użycia paszport odeśle Cię z powrotem na pierwotnie żądany adres URL lub z powrotem na podany adres URL.

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

igneozaur
źródło
Wygląda na to, że jest to duplikat odpowiedzi @ jaredhanson
mikemaccana
1

Odpowiedzi @chovy i @linuxdan mają błąd polegający na braku czyszczenia, session.returnTojeśli użytkownik przejdzie do innej strony po przekierowaniu logowania (to nie wymaga uwierzytelnienia) i zaloguje się przez nią. Więc dodaj ten kod do ich implementacji:

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

Jeśli wykonujesz jakieś żądania AJAX ze strony logowania, możesz je również wykluczyć.


Innym sposobem jest użycie lampy błyskowej wensureAuthenticated

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

A następnie w GET login

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

W widoku dodaj ukryte pole do formularza logowania (przykład w jade)

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

Logowanie POST

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

Zauważ, że redirectTo zostanie wyczyszczone po pierwszym zalogowaniu GET.

Aleksandra Daniłowa
źródło
0

Najłatwiejszym (i właściwie) sposobem osiągnięcia tego jest ustawienie failureRedirecti successRedirectopcje .

Eray
źródło
Czy możesz rozwinąć? Widzę, successRedirectże jestem używany na trasie powrotnej, ale wtedy zamierzony cel (tj. returnToPowyżej) zostałby utracony, prawda?
Tom Söderlund