最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How do I handle errors in passport.deserializeUser()? - Stack Overflow

programmeradmin1浏览0评论

I've got Express and Passport configured like so:

var express = require("express");
var site = express();
var flash = require("connect-flash");
var passport = require("passport");

site.use(require("cookie-parser")());
site.use(require("body-parser").urlencoded({extended:false}));
site.use(require("express-session")(...));
site.use(flash());
site.use(passport.initialize());
site.use(passport.session());

I've got a pretty stock implementation of deserializeUser (I'm using local authentication backed by MySQL via Bookshelf):

var db = require("./database.js"); // exports models e.g. db.User

passport.deserializeUser(function(id, done) {
    db.User.findById(id).then(function (user) {
        done(null, user);
    }).catch(function (err) {
        done(err, null);
    });
});

I'm running in to the following specific problem: When a logged-in user is deleted from the database (happens when e.g. a site administrator deletes the user), the deserialization fails, as expected, with a CustomError("EmptyResponse") from Bookshelf. However, I don't know how to handle it; done(err, null) ultimately just causes the error message and stack trace to get sent back as HTML to the client.

The question is: How can I provide custom, graceful error handling from deserializeUser on a failure?

Now, if it simplifies things, I could add {require:false} to the db.User.findById call to give me a null user instead of an error, but I still don't know how to handle it (and also I still do need to handle error objects e.g. if the database server is down and there's a connection error).

The action I want to take on failure is to redirect the user back to the login page, potentially with a descriptive flash message, but I don't have any access to the request/response in deserializeUser and I'm not sure how to municate back.

I've got Express and Passport configured like so:

var express = require("express");
var site = express();
var flash = require("connect-flash");
var passport = require("passport");

site.use(require("cookie-parser")());
site.use(require("body-parser").urlencoded({extended:false}));
site.use(require("express-session")(...));
site.use(flash());
site.use(passport.initialize());
site.use(passport.session());

I've got a pretty stock implementation of deserializeUser (I'm using local authentication backed by MySQL via Bookshelf):

var db = require("./database.js"); // exports models e.g. db.User

passport.deserializeUser(function(id, done) {
    db.User.findById(id).then(function (user) {
        done(null, user);
    }).catch(function (err) {
        done(err, null);
    });
});

I'm running in to the following specific problem: When a logged-in user is deleted from the database (happens when e.g. a site administrator deletes the user), the deserialization fails, as expected, with a CustomError("EmptyResponse") from Bookshelf. However, I don't know how to handle it; done(err, null) ultimately just causes the error message and stack trace to get sent back as HTML to the client.

The question is: How can I provide custom, graceful error handling from deserializeUser on a failure?

Now, if it simplifies things, I could add {require:false} to the db.User.findById call to give me a null user instead of an error, but I still don't know how to handle it (and also I still do need to handle error objects e.g. if the database server is down and there's a connection error).

The action I want to take on failure is to redirect the user back to the login page, potentially with a descriptive flash message, but I don't have any access to the request/response in deserializeUser and I'm not sure how to municate back.

Share Improve this question edited Dec 9, 2016 at 22:10 Jason C asked Dec 9, 2016 at 22:03 Jason CJason C 40.4k15 gold badges135 silver badges198 bronze badges 2
  • Can you just check for the message in the error (err.message) and send back some custom response that looks nice for the user? (In the catch callback) – Josh Beam Commented Dec 9, 2016 at 22:06
  • @JoshBeam That's what I want to do, but how do I access the response object from there? It's not passed as a parameter to deserializeUser. – Jason C Commented Dec 9, 2016 at 22:06
Add a ment  | 

2 Answers 2

Reset to default 17

I figured out one solution. Errors here can be handled in an Express middleware error-handler. So, for example:

// Note: Must be used *after* passport middleware.
site.use(function(err, req, res, next) {
    if (err) {
        // Handle deserialization errors here.
    } else {
        next();
    }
});

One important caveat though is if deserialization failed non-recoverably, all routes accessed through passport (even ones that don't require authentication) will continue to fail, which most likely also includes your login and logout endpoints, thus permanently breaking access until the user clears their cookies. So you have to force a logout, it's the only real way to recover:

site.use(function(err, req, res, next) {
    if (err) {
        req.logout(); // So deserialization won't continue to fail.
    } else {
        next();
    }
});

Further building on that, in my case I wanted to redirect back to the login page with a flash message. But you have to be careful: If an error occurs on the login page itself you need to avoid infinite redirects, so:

site.use(function(err, req, res, next) {
    if (err) {
        req.logout();
        if (req.originalUrl == "/loginpage") {
            next(); // never redirect login page to itself
        } else {
            req.flash("error", err.message);
            res.redirect("/loginpage");
        }
    } else {
        next();
    }
});

And of course you probably want to only do this for CustomError("EmptyResponse"), or even redo the error handling in your deserialize implementation to have it throw your own more specific error.

Kind of a weird approach, but seems to get the job done. Open to cleaner suggestions.

In the Passport docs "Configure" section, they show the ability to send a specific message:

return done(null, false, { message: 'Incorrect username.' });

The above example is in their docs, but in your case, you could do something like:

passport.deserializeUser(function(id, done) {
    db.User.findById(id).then(function (user) {
        done(null, user);
    }).catch(function (err) {
        done(err, null, { message: 'User does not exist' });
    });
});
发布评论

评论列表(0)

  1. 暂无评论