I am trying to implement a normal login and signup module and I am facing difficulty in understanding the done function.here is the line I feel is responsible for the done function. Now what I wanted to do was return the corresponding messages like if there was some server error.
app.post('/user/auth/login', passport.authenticate('local-login'), function(req, res) {
console.log("Login Request Successful");
if(req.user){
res.status(200).send(JSON.stringify({
'msg' : "Successfully logged in"
}));
}
else{
res.status(400)
}
//console.log(req.user);
});
This is for the case when after logging in Passport attaches a user object to the request module.
But how do I differentiate between a server error and authentication failed error
Here is my authenticate middleware.
passport.use('local-login', new LocalStrategy({
passReqToCallback: true
}, function(req, username, password, done) {
Users.findOne({
'emailId': username
}, function(err, user) {
if (err)
return done(err);
if (!user)
return done(null, false);
else if (passwordHash.verify(password, user.password)) {
console.log("User is verified");
req.session.save();
return done(null, user);
} else
return done(null, false);
});
}));
Basically, I need to access the messages in done() function. How do I do that?
How does function, like if I type the wrong password, the message I get in my browser in Unauthorized. It means somewhere it is setting the response.data field to Unauthorized. Instead of that I want to know when there is an error and want to send my custom message.
I don't know what do you mean by
access the messages in done() function
, however You can very well do with supplying addition object with message in done callback
if (!user) {
return done(null, false, {
message: 'Unknown user or invalid password'
});
}
if (!user.authenticate(password)) {
return done(null, false, {
message: 'Unknown user or invalid password'
});
}
Another method.
I don't know how I missed it. There is an option of custom callbacks in the docs. Here is the implementation of the same.
app.post('/user/auth/login', function(req, res) {
passport.authenticate('local-login', function(err, user, info) {
if (err) {
res.status(500).send(JSON.stringify({
'msg': "Internal Server Error"
}));
}
if (!user) {
res.status(401).send(JSON.stringify({
'msg': "Username or Password is incorrect"
}));
}
if (user) {
res.status(200).send(JSON.stringify({
'msg': "Successfully logged in"
}));
}
})(req, res, next);
});
The only issue with this solution is that you will have to manually login and create session and all that.
An optional callback can be supplied to allow the application to overrride the default manner in which authentication attempts are
handled. The callback has the following signature, where user
will be set to the authenticated user on a successful
authentication attempt, or false otherwise. An optional info
argument will be passed, containing additional details provided by
the strategy's verify callback.
app.get('/protected', function(req, res, next) {
passport.authenticate ('local',function(err, user, info) {
if (err) { return next(err) }
if (!user) { return res.redirect('/signin') }
res. redirect('/account');
})(req, res, next);
});
Note that if a callback is supplied, it becomes the application's responsibility to log-in the user, establish a session, and otherwise
perform the desired operations.
Related
I'm using express + passport to handle authentication for a react app. Because I'm using react, I don't want to automatically redirect the login request via express, instead I want to return JSON.
However, I've noticed that simply returning JSON and then calling next(null, user) seems to prevent passport from running the serializeUser method. By calling next passport should automatically call the req.login function, which doesn't appear to happen when I've sent json.
It appears that if I manually call req.login and then send the JSON, it works (the serialize method is called, the user has a session).
Is there a way to send a json response and call next without having to manually invoke req.login? Feels like I'm just not doing things the correct way.
module.exports = function(app) {
app.post('/login', (req, res, next) => {
passport.authenticate('local', (err, user) => {
if (err) {
res.json({ errorMessage: err.message });
return next(err);
}
if (!user) {
res.json({ errorMessage: 'Invalid user.' });
return next(err);
}
// This fails to invoke the session serialization:
// res.json({
// success: true,
// username: user.username
// });
// return next(null, user);
// This works:
req.logIn(user, loginErr => {
if (loginErr) {
return next(loginErr);
}
res.json({
success: true,
username: user.username
});
return;
});
})(req, res, next);
});
};
I am using passport.js to authenticate a user upon login in my node js app. So I am also using express framework and mongo db.
This is my route:
router.post('/login',
passport.authenticate('local'),
function(req, res, err) {
var query = {
'username': req.user.username
};
var update = {
last_login_date: Date.now()
};
var options = {
new: true
};
User.findOneAndUpdate(query, update, options, function(err, user) {
if (err) {
res.json(err);
} else {
res.json('Welcome ' + req.user.username);
}
});
});
And this is my passport code:
passport.use(new LocalStrategy(
function(username, password, done) {
User.getUserByUsername(username, function(err, user) {
if (err) throw err;
if (!user) {
return done(null, false, {
message: 'User does not exist'
});
}
User.comparePassword(password, user.password, function(err, isMatch) {
if (err) throw err;
if (isMatch) {
return done(null, user);
} else {
return done(null, false, {
message: 'Invalid password'
});
}
});
});
}));
So, when a user logs in using the correct credentials, I get the following printed out:
Welcome 'username'
However, in the following scenarios, my code does not print out custom messages:
Password Username Response Should be:
INCORRECT CORRECT Unauthorized Invalid password
CORRECT INCORRECT Unauthorized User does not exist
INCORRECT INCORRECT Unauthorized User does not exist
According to passport.js documentation:
By default, if authentication fails, Passport will respond with a 401
Unauthorized status, and any additional route handlers will not be
invoked.
However, you can have your own custom callback messages instead of Unauthorized, which does not seem to work for me.
Any help will be appreciated!
I am trying to send the info message that gets set in my verify callback:
Here is the example from passport docs:
var passport = require('passport')
, LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Incorrect username.' });
}
if (!user.validPassword(password)) {
return done(null, false, { message: 'Incorrect password.' });
}
return done(null, user);
});
}
));
However if I write my route as:
app.post('/login',
passport.authenticate('local'),
function(req, res) {
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user.
res.redirect('/users/' + req.user.username);
});
That function does not get called with the info message. At least not that I know of. I know passport shoves the user into the req, as I can access it from req.user. Is there a way to access the info message like this. Or do I need to specify a custom callback?
Which they outline as:
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);
});
The part that is confusing is that they outline using a 3rd parameter in the done callback (from verify) that is a message, yet you can only access that if you write a custom callback. Is that true.
The adopted convention in the Passport framework is that the optional info object will be available through req.authInfo.
This however depends on the used strategy which is responsible for passing it to the Passport framework. For instance, the passport-http strategy does not forward the info object to Passport while the passport-local does.
Here is how you can then access it in a controller protected by the local strategy :
app.post('/login',
passport.authenticate('local'),
function (req, res) {
if (req.authInfo) {
// req.authInfo.message will contain your actual message.
}
});
I have no login page but rather I have a login form that appears on every page. I want to redirect user back to the same page they were on regardless of whether authentication was successful (with appropriate flash messages)
Take the following code:
app.post('/login', validateLogin, passport.authenticate('local-login'), function(req, res) {
var redirectUrl = '/';
if(req.body.to.length > 0){
redirectUrl = req.body.to;
}
console.log("THIS IS ONLY CALLED IF passport.authenticate() IS SUCCESSFUL");
res.redirect(redirectUrl);
});
I only see the final middleware above being called if authentication is passed. If it fails then passport appears to be redirecting me to /login in the form of a get request. In my app this page doesn't exist.
If I pass an additional options object as a parameter in the passport authenticate function then this works:
app.post('/login', validateLogin, passport.authenticate('local-login', {
successRedirect : '/', // redirect to the secure profile section
failureRedirect : '/signup', // redirect back to the signup page. THIS IS JUST FOR TESTING TO SEE IF THE REDIRECT ON FAIL WORKS.
failureFlash : true, // allow flash messages
}
));
But in doing this I lose the ability to choose where to redirect the user to. It seems that passport takes control over where the user is redirected to if authentication fails. How can I fix this? Or is it a bug? Must passport authenticate be the last middleware in the chain if authentication fails?
This is my local strategy function call:
//LOCAL LOGIN
passport.use('local-login', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function(req, email, password, done) { // callback with email and password from our form
console.log("IN PASSPORT");
if(email.length == 0 || password.length == 0){
console.log("FIELDS ARE EMPTY");
return done(null, false, req.flash('loginMessage', 'Fill in all values.'));
}
// find a user whose email is the same as the forms email
// we are checking to see if the user trying to login already exists
User.findOne({ 'local.email' : email }, function(err, user) {
// if there are any errors, return the error before anything else
if (err){
return done(err);
console.log("db err");
}
// if no user is found, return the message
if (!user){
console.log("not user");
return done(null, false, req.flash('loginMessage', 'Incorrect details.')); // req.flash is the way to set flashdata using connect-flash
}
// if the user is found but the password is wrong
if (!user.validPassword(password)){
console.log("invalid pw");
return done(null, false, req.flash('loginMessage', 'Incorrect details.')); // create the loginMessage and save it to session as flashdata
}
// all is well, return successful user
console.log("All OK");
return done(null, user);
});
}));
You could use a custom authentication callback as described in the last paragraph there http://passportjs.org/guide/authenticate/.
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) { return next(err); }
// Redirect if it fails
if (!user) { return res.redirect('/login'); }
req.logIn(user, function(err) {
if (err) { return next(err); }
// Redirect if it succeeds
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
I was running into the same issue where the redirect-calls , that follow successful Facebook Auth
passport.authenticate('facebook', ..)
.. were not being honored.
Based on 'local' passportJS Strategy - and a nice reminder of that from #ploutch's answer here .. I realized the key to getting it to work seems to be in this call:
req.logIn(user, function(err) {
...
}
For Facebook, this route setup worked for me:
app.get(
'/auth/facebook/callback',
passport.authenticate
(
'facebook',
{ failureRedirect: '/fbFailed' }
),
function(req, res)
{
var user = myGetUserFunc(); // Get user object from DB or etc
req.logIn(user, function(err) {
if (err) {
req.flash('error', 'SOMETHING BAD HAPPEND');
return res.redirect('/login');
}
req.session.user = user;
// Redirect if it succeeds
req.flash('success', 'Fb Auth successful');
return res.redirect('/user/home');
});
}
);
Full answer, including:
Middleware to set redirectUrl
Flash messages
Not returning values that won't be used
Just create a redirectTo value in your loginRequired middleware:
var loginRequired = function(req, res, next) {
if ( req.isAuthenticated() ) {
next();
return
}
// Redirect here if logged in successfully
req.session.redirectTo = req.path;
res.redirect('/login')
}
And then in your login POST:
router.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if ( err ) {
next(err);
return
}
// User does not exist
if ( ! user ) {
req.flash('error', 'Invalid email or password');
res.redirect('/login');
return
}
req.logIn(user, function(err) {
// Invalid password
if ( err ) {
req.flash('error', 'Invalid email or password');
next(err);
return
}
res.redirect(req.session.redirectTo || '/orders');
return
});
})(req, res, next);
});
I know this might still be a problem for some people like me who tried all the suggested options without any success.
In my case, as it turned out, I was getting the error because my req.body object was always empty. I had my body parsing middleware set up correctly so it didn't make sense why this was happening.
After more research I found out that the enctype I was using for my forms(multipart/form-data) isn't supported by body-parser - see their read me - after switching to a different middleware, multer, everything worked smoothly.
I've looked at how error handling should work in node via this question Error handling principles for Node.js + Express.js applications?, but I'm not sure what passport's doing when it fails authentication. I have the following LocalStrategy:
passport.use(new LocalStrategy({ usernameField: 'email', passwordField: 'password' },
function(email, password, next) {
User.find({email: UemOrUnm}, function(err, user){
if (err) { console.log('Error > some err'); return next(err); }
if (!user) { console.log('Error > no user'); return next('Incorrect login or password'); }
if (password != user.password) {
return next(Incorrect login or password);
}
return next(null, user);
});
}
));
After I see 'Error > some err' console printout, nothing else happens. I would think it should continue on the the next path with an error parameter, but it doesn't seem to do that. What's going on?
The strategy-implementation works in conjunction with passport.authenticate to both authenticate a request, and handle success/failure.
Say you're using this route (which is passed an e-mail address and a password):
app.post('/login', passport.authenticate('local', {
successRedirect: '/loggedin',
failureRedirect: '/login', // see text
failureFlash: true // optional, see text as well
});
This will call the code in the strategy, where one of three conditions can happen:
An internal error occurred trying to fetch the users' information (say the database connection is gone); this error would be passed on: next(err); this will be handled by Express and generate an HTTP 500 response;
The provided credentials are invalid (there is no user with the supplied e-mail address, or the password is a mismatch); in that case, you don't generate an error, but you pass a false as the user object: next(null, false); this will trigger the failureRedirect (if you don't define one, a HTTP 401 Unauthorized response will be generated);
Everything checks out, you have a valid user object, so you pass it along: next(null, user); this will trigger the successRedirect;
In case of an invalid authentication (but not an internal error), you can pass an extra message along with the callback:
next(null, false, { message : 'invalid e-mail address or password' });
If you have used failureFlash and installed the connect-flash middleware, the supplied message is stored in the session and can be accessed easily to, for example, be used in a template.
EDIT: it's also possible to completely handle the result of the authentication process yourself (instead of Passport sending a redirect or 401):
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err); // will generate a 500 error
}
// Generate a JSON response reflecting authentication status
if (! user) {
return res.send({ success : false, message : 'authentication failed' });
}
// ***********************************************************************
// "Note that when using a custom callback, it becomes the application's
// responsibility to establish a session (by calling req.login()) and send
// a response."
// Source: http://passportjs.org/docs
// ***********************************************************************
req.login(user, loginErr => {
if (loginErr) {
return next(loginErr);
}
return res.send({ success : true, message : 'authentication succeeded' });
});
})(req, res, next);
});
What Christian was saying was you need to add the function
req.login(user, function(err){
if(err){
return next(err);
}
return res.send({success:true});
});
So the whole route would be:
app.post('/login', function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) {
return next(err); // will generate a 500 error
}
// Generate a JSON response reflecting authentication status
if (! user) {
return res.send(401,{ success : false, message : 'authentication failed' });
}
req.login(user, function(err){
if(err){
return next(err);
}
return res.send({ success : true, message : 'authentication succeeded' });
});
})(req, res, next);
});
source: http://passportjs.org/guide/login/
You need to add req.logIn(function (err) { }); and do the success redirect inside the callback function
Some time has passed and now the most right code will be:
passport.authenticate('local', (err, user, info) => {
if (err) {
return next(err); // will generate a 500 error
}
// Generate a JSON response reflecting authentication status
if (!user) {
return res.status(401).send({ error: 'Authentication failed' });
}
req.login(user, (err) => {
if (err) {
return next(err);
}
return res.status(202).send({ error: 'Authentication succeeded' });
});
});
I found this thread very useful!
https://github.com/jaredhanson/passport-local/issues/2
You could use this to return error and render it in form.
app.post('/login',
passport.authenticate('local', { successRedirect: '/home', failWithError: true }),
function(err, req, res, next) {
// handle error
return res.render('login-form');
}
);
This is what I got after console.log(req) at the failler route.
const localStrategy = new LocalStrategy({ usernameField: "email" }, verifyUser);
passport.use(localStrategy);
const authenticateWithCredentials = passport.authenticate("local", {
failureRedirect: "/api/auth/login-fail",
failureMessage: true,
});
validation method find your user from db and throw error to the cb if there is any
const verifyUser = async (email, password, cb) => {
const user = await User.findOne({ email });
if (!user) return cb(null, false, { message: "email/password incorrect!" });
const isMatched = await user.comparePassword(password);
if (!isMatched)
return cb(null, false, { message: "email/password incorrect!" });
cb(null, {
id: user._id,
email,
name: user.name,
});
};
now setup your route
router.post("/sign-in", authenticateWithCredentials,(req, res) => {
res.json({user: req.user})
});
router.get("/login-fail", (req, res) => {
let message = "Invalid login request!";
// if you are using typescript cast the sessionStore to any
const sessions = req.sessionStore.sessions || {};
for (let key in sessions) {
const messages = JSON.parse(sessions[key])?.messages;
if (messages.length) {
message = messages[0];
break;
}
}
res.status(401).json({ error: message });
});