Zurücksenden einer JSON-Antwort, wenn die Passport.js-Authentifizierung fehlschlägt

Lesezeit: 9 Minuten

Benutzer-Avatar
kurisukun

Ich benutze Node.js als Backend-API-Server für einen iPhone-Client. Ich benutze Passport.js sich authentifizieren mit a local strategy. Der entsprechende Code ist unten:

// This is in user.js, my user model
UserSchema.static('authenticate', function(username, password, callback) {
    this.findOne({ username: username }, function(err, user) {
        if (err){
            console.log('findOne error occurred');
            return callback(err);
        }
        if (!user){
            return callback(null, false);
        }
        user.verifyPassword(password, function(err, passwordCorrect){
            if (err){
                console.log('verifyPassword error occurred');
                return callback(err);
            }
            if (!passwordCorrect){
                console.log('Wrong password');
                return callback(err, false);
            }
            console.log('User Found, returning user');
            return callback(null, user);
        });
    });
});

und

// This is in app.js
app.get('/loginfail', function(req, res){
    res.json(403, {message: 'Invalid username/password'});
});

app.post('/login',
    passport.authenticate('local', { failureRedirect: '/loginfail', failureFlash: false }),
    function(req, res) {
       res.redirect("https://stackoverflow.com/");
});

Im Moment ist es mir gelungen, eine fehlgeschlagene Anmeldung an /loginfail umzuleiten, wo ich etwas JSON an den iPhone-Client zurücksende. Dies hat jedoch nicht genug Granularität. Ich möchte in der Lage sein, die entsprechenden Fehler an den iPhone-Client zurückzusenden, z. B.: “Kein Benutzer gefunden” oder “Passwort ist falsch”. Mit meinem vorhandenen Code sehe ich nicht, wie dies erreicht werden kann.

Ich habe versucht, den Beispielen für einen benutzerdefinierten Rückruf auf der Website „passport.js“ zu folgen, aber ich kann es einfach nicht zum Laufen bringen, weil ich die Knoten nicht verstehe. Wie könnte ich meinen Code so ändern, dass ich eine res.json mit einem entsprechenden Fehlercode/einer entsprechenden Fehlermeldung zurücksenden kann?

Ich versuche jetzt so etwas:

// In app.js
app.post('/login', function(req, res, next) {
    passport.authenticate('local', function(err, user, info) {
        if (err) { return next(err) }
        if (!user) {
            console.log(info);
            // *** Display message without using flash option
            // re-render the login form with a message
            return res.redirect('/login');
        }
        console.log('got user');
        return res.json(200, {user_id: user._id});
    })(req, res, next);
});

// In user.js
UserSchema.static('authenticate', function(username, password, callback) {
    this.findOne({ username: username }, function(err, user) {
        if (err){
            console.log('findOne error occurred');
            return callback(err);
        }
        if (!user){
            return callback(null, false);
        }
        user.verifyPassword(password, function(err, passwordCorrect){
            if (err){
                return callback(err);
            }
            if (!passwordCorrect){
                return callback(err, false, {message: 'bad password'});
            }
            console.log('User Found, returning user');
            return callback(null, user);
        });
    });
});

Aber wenn ich es damals mit console.log(info) versuche, sagt es nur undefiniert. Ich weiß nicht, wie ich diesen benutzerdefinierten Rückruf zum Laufen bekomme … Jede Hilfe wäre willkommen!

  • Ich stolperte auch in diesem Problem für eine Weile. Ihre Lösung ist ähnlich wie benutzerdefinierter Rückruf aus Passport.js doc

    – FisNaN

    8. November 2018 um 2:51 Uhr

  • benutzerdefinierter Rückruf macht es zu Ihrer Verantwortung, eine Sitzung einzurichten. Wenn Sie dies vermeiden möchten, habe ich eine Problemumgehung gefunden, indem ich den Flash-Nachrichtenmechanismus emuliert habe (siehe unten).

    – Tal Tikotzki

    27. April 2020 um 12:28 Uhr

Benutzer-Avatar
Mnebuerquo

Ich hatte ein ähnliches Problem mit Passport und fehlgeschlagene Login-Antworten. Ich habe eine API erstellt und wollte, dass alle Antworten als zurückgegeben werden JSON. Passport antwortet auf ein ungültiges Passwort mit folgendem Status: 401 und Körper: Unauthorized. Das ist nur eine Textzeichenfolge im Körper, kein JSON, also hat es meinen Client kaputt gemacht, der alles JSON erwartet hat.

Wie sich herausstellt, gibt es eine Möglichkeit, Passport dazu zu bringen, den Fehler einfach an das Framework zurückzugeben, anstatt zu versuchen, selbst eine Antwort zu senden.

Die Antwort ist zu setzen failWithError in den zur Authentifizierung übergebenen Optionen:
https://github.com/jaredhanson/passport/issues/126#issuecomment-32333163

Aus Jaredhansons Kommentar in der Ausgabe:

app.post('/login',
  passport.authenticate('local', { failWithError: true }),
  function(req, res, next) {
    // handle success
    if (req.xhr) { return res.json({ id: req.user.id }); }
    return res.redirect("https://stackoverflow.com/");
  },
  function(err, req, res, next) {
    // handle error
    if (req.xhr) { return res.json(err); }
    return res.redirect('/login');
  }
);

Dadurch wird die Fehlerbehandlungsroutine nach Passport-Aufrufen aufgerufen next(err). Für meine App habe ich einen generischen Fehlerhandler geschrieben, der speziell auf meinen Anwendungsfall zugeschnitten ist, bei dem nur ein JSON-Fehler bereitgestellt wird:

// Middleware error handler for json response
function handleError(err,req,res,next){
    var output = {
        error: {
            name: err.name,
            message: err.message,
            text: err.toString()
        }
    };
    var statusCode = err.status || 500;
    res.status(statusCode).json(output);
}

Dann habe ich es für alle API-Routen verwendet:

var api = express.Router();
...
//set up some routes here, attached to api
...
// error handling middleware last
api.use( [
        handleError
        ] );

Ich habe die nicht gefunden failWithError Option in der Dokumentation. Ich bin darauf gestoßen, als ich den Code im Debugger durchgegangen bin.

Bevor ich das herausfand, habe ich auch den in der @Kevin_Dente-Antwort erwähnten “benutzerdefinierten Rückruf” ausprobiert, aber er hat bei mir nicht funktioniert. Ich bin mir nicht sicher, ob das für eine ältere Version von Passport war oder ob ich es einfach falsch gemacht habe.

  • noch gültig in 2017 LOL

    – Edwin O.

    18. April 2017 um 0:21 Uhr

  • noch gültig. 😛

    – Toan Tran

    19. Juli 2017 um 7:44 Uhr

  • 2021 noch gültig

    – AndroConsis

    9. Dezember 2021 um 9:21 Uhr

Ich glaube, dass die Callback-Funktion, die Ihre statischen Aufrufe “authentifizieren” (in Ihrem Code als “Callback” bezeichnet) einen dritten Parameter – “info” – akzeptiert, den Ihr Code bereitstellen kann. Anstatt das Objekt { failureRedirect: …} zu übergeben, übergeben Sie dann eine Funktion, die 3 Argumente akzeptiert – err, user und info. Die “Informationen”, die Sie in Ihrer Authentifizierungsmethode angegeben haben, werden an diesen Rückruf weitergeleitet.

Passport bezeichnet dieses Szenario als „benutzerdefinierten Rückruf“. Siehe die Dokumente hier:
http://passportjs.org/guide/authenticate/

  • Danke dafür. Ich bin mir des benutzerdefinierten Rückrufs bewusst, aber mein Problem ist, dass ich einfach nicht verstehe, wie ich ihn implementieren soll (sorry, sehr neu in node und js). Ich habe meine Frage mit meinen neuesten Bemühungen bearbeitet. Wie Sie sehen, versuche ich, diesen “info”-Parameter zu verwenden, aber ich weiß nicht, wie ich ihn richtig verwenden soll – er ist immer undefiniert, wenn ich von der Authentifizierung zurückkomme.

    – kurisukun

    14. März 2013 um 2:53 Uhr

  • Tut mir leid, ich habe gerade herausgefunden, dass ich auch “info” hinzufügen muss: Passport.use(new LocalStrategy(function(username, password, done) { User.authenticate(username, password, function(err, user, info ) { return done(err, user, info); }); } ));

    – kurisukun

    14. März 2013 um 2:55 Uhr

  • Dieses Gespräch hat mir geholfen, mit etwas mehr Substanz zum gleichen Schluss zu kommen. github.com/jaredhanson/passport/issues/255

    – jpierson

    11. Januar 2016 um 20:43 Uhr

  • @kurisukun Warum hast du (req, res, next) in Passport.Authenticate bestanden?

    – Muthu

    14. Juli 2017 um 5:05 Uhr

  • Könnten Sie für die Zukunft ein paar Code-Snippets hinzufügen, um dies zu verdeutlichen? Das ist ohne sie etwas schwer zu analysieren.

    – Nik

    18. Mai 2018 um 4:30 Uhr

Es gibt eine offizielle Dokumentation für Benutzerdefinierter Rückruf:

app.get('/login', function(req, res, next) {
  passport.authenticate('local', function(err, user, info) {
    if (err) { return next(err); }
    if (!user) { return res.redirect('/login'); }
    req.logIn(user, function(err) {
      if (err) { return next(err); }
      return res.redirect('/users/' + user.username);
    });
  })(req, res, next);
});

https://github.com/passport/www.passportjs.org/blob/master/views/docs/authenticate.md

Gemäß der offiziellen Dokumentation von Passport Sie können verwenden benutzerdefinierter Rückruf Funktion, um den Fall einer fehlgeschlagenen Autorisierung zu behandeln und die Standardnachricht zu überschreiben.

Wenn Sie eine REST-API entwickeln und dann eine hübsche JSON-Antwort wie folgt senden möchten:

{
    "error": {
        "name": "JsonWebTokenError",
        "message": "invalid signature"
    },
    "message": "You are not authorized to access this protected resource",
    "statusCode": 401,
    "data": [],
    "success": false
}

Ich benutzte Passport JWT Authentifizierung zur Sicherung einiger meiner Routen und wurde angewendet authMiddleware wie nachstehend:

app/middlewares/authMiddleware.js

const express = require('express');
const router = express.Router();
const passport = require('passport');
const _ = require('lodash');

router.all('*', function (req, res, next) {
  passport.authenticate('local', function(err, user, info) {

    // If authentication failed, `user` will be set to false. If an exception occurred, `err` will be set.
    if (err || !user || _.isEmpty(user)) {
      // PASS THE ERROR OBJECT TO THE NEXT ROUTE i.e THE APP'S COMMON ERROR HANDLING MIDDLEWARE
      return next(info);
    } else {
      return next();
    }
  })(req, res, next);
});

module.exports = router;

app/routes/approutes.js

const authMiddleware = require('../middlewares/authMiddleware');

module.exports = function (app) {
  // secure the route by applying authentication middleware
  app.use("https://stackoverflow.com/users", authMiddleware);
  .....
  ...
  ..

  // ERROR-HANDLING MIDDLEWARE FOR SENDING ERROR RESPONSES TO MAINTAIN A CONSISTENT FORMAT
  app.use((err, req, res, next) => {
    let responseStatusCode = 500;
    let responseObj = {
      success: false,
      data: [],
      error: err,
      message: 'There was some internal server error',
    };

    // IF THERE WAS SOME ERROR THROWN BY PREVIOUS REQUEST
    if (!_.isNil(err)) {
      // IF THE ERROR IS REALTED TO JWT AUTHENTICATE, SET STATUS CODE TO 401 AND SET A CUSTOM MESSAGE FOR UNAUTHORIZED
      if (err.name === 'JsonWebTokenError') {
        responseStatusCode = 401;
        responseObj.message="You are not authorized to access this protected resource";
      }
    }

    if (!res.headersSent) {
      res.status(responseStatusCode).json(responseObj);
    }
  });
};

Benutzer-Avatar
RubenJMarrufo

Sie können dies ohne benutzerdefinierte Rückrufe mit der Eigenschaft tun passReqToCallback in Ihrer Strategiedefinition:

passport.use(new LocalStrategy({passReqToCallback: true}, validateUserPassword));

Dann können Sie Ihren benutzerdefinierten Authentifizierungsfehlercode zur Anfrage in Ihrem Strategiecode hinzufügen:

var validateUserPassword = function (req, username, password, done) {
    userService.findUser(username)
        .then(user => {
            if (!user) {
                req.authError = "UserNotFound";
                return done(null, false);
            }

Und schließlich können Sie diese benutzerdefinierten Fehler in Ihrer Route behandeln:

app.post('/login', passport.authenticate('local', { failWithError: true })      
    function (req, res) {
        ....
    }, function(err, req, res, next) {
        if(req.autherror) {
            res.status(401).send(req.autherror)
        } else {
            ....
        }
    }
);

Benutzer-Avatar
Tal Tikotzki

Eine kurze Problemumgehung besteht darin, den Flash-Methodenaufruf zu emulieren, der ursprünglich Connect-Flash unterstützen sollte, und diese Methode zu verwenden, um das JSON-Objekt zurückzugeben.

definieren Sie zuerst den “Emulator”:

var emulateFlash = function (req, res, next) {
        req.flash = (type, message) => {
            return res.status(403).send({ status: "fail", message });
        }
        next();
    }

Dadurch wird die Flash-Methode eingefügt, die bei einem Fehler das Fehler-JSON-Objekt sendet.

Gehen Sie in der Route wie folgt vor:

Verwenden Sie zunächst den Emulator auf breiter Front mit:

router.use(emulateFlash);

Man kann stattdessen die emulateFlash-Methode für jede benötigte Route verwenden.

2. Geben Sie auf der Route bei der Verwendung von Authenticate die Option failureFlash mit einer Nachricht an:

router.route("/signin")
    .post(.authenticate('local', { session: false,  failureFlash: "Invalid email or password."}), UsersController.signIn);

Ich habe dies sowohl auf fehlgeschlagene als auch auf erfolgreiche Authentifizierung getestet und festgestellt, dass es funktioniert. Beim Betrachten des Codes konnte ich keine andere Möglichkeit finden, ein Objekt zurückzugeben, als die Callback-Methode zu implementieren, die viel mehr Arbeit erfordert.

1049490cookie-checkZurücksenden einer JSON-Antwort, wenn die Passport.js-Authentifizierung fehlschlägt

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy