web-dev-qa-db-de.com

Senden einer JSON-Antwort zurück, wenn die Passport.js-Authentifizierung fehlschlägt

Ich benutze Node.js als Backend-API-Server für einen iPhone-Client. Ich benutze Passport.js zur Authentifizierung mit einem local strategy. Der relevante 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('/');
});

Im Moment ist es mir gelungen, eine fehlgeschlagene Anmeldung an/loginfail umzuleiten, wo ich einige JSON-Daten an den iPhone-Client zurücksende. Dies hat jedoch nicht genügend 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, kann ihn jedoch aufgrund mangelnden Knotenverständnisses nicht zum Funktionieren bringen. Wie kann ich meinen Code so ändern, dass ich eine res.json mit einem entsprechenden Fehlercode/einer entsprechenden Fehlermeldung zurücksenden kann?

EDIT: Ich versuche so etwas jetzt:

// 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 zurück zu console.log (Info) versuche, sagt es gerade undefined. Ich weiß nicht, wie ich diesen benutzerdefinierten Rückruf zum Laufen bringen kann ... Für jede Hilfe wäre ich dankbar!

52
kurisukun

Ich glaube, die Rückruffunktion, mit der Sie statische Aufrufe authentifizieren (in Ihrem Code als "Rückruf" bezeichnet), akzeptiert einen dritten Parameter - "info" -, den Ihr Code bereitstellen kann. Anstatt das Objekt {failureRedirect: ...} zu übergeben, übergeben Sie eine Funktion, die drei Argumente akzeptiert - err, user und info. Die "Informationen", die Sie in Ihrer Authentifizierungsmethode angegeben haben, werden an diesen Rückruf weitergeleitet.

Passport nennt dieses Szenario "benutzerdefinierten Rückruf". Lesen Sie die Dokumente hier: http://passportjs.org/guide/authenticate/

28
Kevin Dente

Ich hatte ein ähnliches Problem mit Passport und konnte mich nicht anmelden. Ich habe eine API erstellt und wollte, dass alle Antworten als JSON zurückgegeben werden. Passport antwortet auf ein ungültiges Passwort mit dem Status: 401 Und body: Unauthorized. Das ist nur eine Textzeichenfolge im Textkörper, nicht JSON, also hat sie meinen Client beschädigt, der alle JSON-Anforderungen erfüllt 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, failWithError in den zur Authentifizierung übergebenen Optionen festzulegen: https://github.com/jaredhanson/passport/issues/126#issuecomment-3233316

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('/');
  },
  function(err, req, res, next) {
    // handle error
    if (req.xhr) { return res.json(err); }
    return res.redirect('/login');
  }
);

Dadurch wird die Fehlerbehandlungsroutine aufgerufen, nachdem Passport next(err) aufgerufen hat. Für meine App habe ich einen allgemeinen Fehlerbehandler geschrieben, der speziell auf meinen Anwendungsfall zugeschnitten ist, bei dem nur ein JSON-Fehler ausgegeben 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 Option failWithError in der Dokumentation nicht gefunden. Ich bin darauf gestoßen, als ich den Code im Debugger durchgesehen habe.

Außerdem habe ich, bevor ich das herausgefunden habe, den in der Antwort von @Kevin_Dente erwähnten "benutzerdefinierten Rückruf" ausprobiert, aber er hat bei mir nicht funktioniert. Ich bin nicht sicher, ob das für eine ältere Version von Passport war oder ob ich es nur falsch gemacht habe.

59
Mnebuerquo

Es gibt eine offizielle Dokumentation zu Custom Callback :

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

3
Arthur Araújo

Sie können dies ohne benutzerdefinierte Rückrufe tun, indem Sie die Eigenschaft passReqToCallback in Ihrer Strategiedefinition verwenden:

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

Anschließend können Sie Ihren benutzerdefinierten Authentifizierungsfehlercode zur Anforderung 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 auf 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 {
            ....
        }
    }
);
1
RubenJMarrufo

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

Wenn Sie 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, um einige meiner Routen abzusichern, und es wurde authMiddleware wie folgt angewendet:

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('/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);
    }
  });
};
0
Rahul Gupta