web-dev-qa-db-fra.com

Renvoi d'une réponse JSON en cas d'échec de l'authentification Passport.js

J'utilise Node.js comme serveur API backend pour un client iPhone. J'utilise Passport.js pour s'authentifier avec un local strategy. Le code correspondant est ci-dessous:

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

et

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

À l'heure actuelle, j'ai réussi à rediriger une connexion échouée vers/loginfail, où je renvoie du JSON au client iPhone. Cependant, cela n'a pas assez de granularité. Je veux pouvoir renvoyer les erreurs appropriées au client iPhone, telles que: "Aucun utilisateur trouvé" ou "Le mot de passe est incorrect". Avec mon code existant, je ne vois pas comment cela peut être accompli.

J'ai essayé de suivre les exemples d'un rappel personnalisé sur le site passport.js, mais je n'arrive pas à le faire fonctionner en raison du manque de compréhension des nœuds. Comment pourrais-je modifier mon code afin de pouvoir renvoyer un res.json avec un code/message d'erreur approprié?

EDIT: J'essaye quelque chose comme ça maintenant:

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

Mais quand j'essaye de console.log (info), ça dit juste indéfini. Je ne sais pas comment faire fonctionner ce rappel personnalisé ... Toute aide serait appréciée!

52
kurisukun

Je crois que la fonction de rappel que vos appels statiques "authentifiés" (appelés "rappel" dans votre code) acceptent un troisième paramètre - "info" - que votre code peut fournir. Ensuite, au lieu de passer dans l'objet {failureRedirect: ...}, passez une fonction qui prend 3 arguments - err, user et info. Les "informations" que vous avez fournies dans votre méthode d'authentification seront transmises à ce rappel.

Passport appelle ce scénario "rappel personnalisé". Voir les documents ici: http://passportjs.org/guide/authenticate/

28
Kevin Dente

J'ai rencontré un problème similaire avec Passport et j'ai échoué les réponses de connexion. Je construisais une API et je voulais que toutes les réponses soient renvoyées sous la forme JSON. Le passeport répond à un mot de passe invalide avec le statut: 401 Et le corps: Unauthorized. C'est juste une chaîne de texte dans le corps, pas JSON, donc ça a cassé mon client qui attendait tout JSON.

En fait, il existe un moyen de faire en sorte que Passport renvoie simplement l'erreur au framework au lieu d'essayer d'envoyer une réponse elle-même.

La réponse est de définir failWithError dans les options passées pour authentifier: https://github.com/jaredhanson/passport/issues/126#issuecomment-3233316

D'après le commentaire de jaredhanson dans le numéro:

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

Cela invoquera le gestionnaire d'erreurs après que Passport ait appelé next(err). Pour mon application, j'ai écrit un gestionnaire d'erreurs générique spécifique à mon cas d'utilisation de fournir simplement une erreur JSON:

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

Ensuite, je l'ai utilisé pour toutes les routes api:

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

Je n'ai pas trouvé l'option failWithError dans la documentation. Je suis tombé dessus en traçant le code dans le débogueur.

De plus, avant de comprendre cela, j'ai essayé le "rappel personnalisé" mentionné dans la réponse @Kevin_Dente, mais cela n'a pas fonctionné pour moi. Je ne sais pas si c'était pour une ancienne version de Passport ou si je me trompais.

59
Mnebuerquo

Il existe une documentation officielle pour Rappel personnalisé :

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

Vous pouvez le faire sans rappels personnalisés en utilisant la propriété passReqToCallback dans votre définition de stratégie:

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

Ensuite, vous pouvez ajouter votre code d'erreur d'authentification personnalisé à la demande dans votre code de stratégie:

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

Et enfin, vous pouvez gérer ces erreurs personnalisées dans votre itinéraire:

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

Conformément à la documentation officielle de Passport, vous pouvez utiliser la fonction rappel personnalisé pour gérer le cas d'échec de l'autorisation et remplacer le message par défaut.

Si vous développez REST API et que vous souhaitez envoyer une jolie réponse JSON comme ci-dessous:

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

J'utilisais Passport JWT l'authentification pour sécuriser certains de mes itinéraires et a été appliqué le authMiddleware comme ci-dessous:

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