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 communicate back.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With