I check if the email already exist when registering a user. If a user already exist I pass the error message "email already exists". But in the front end it shows "Unauthorized" error 401. I want to pass the error message which I pass to the front end from the back end but it pass the default message. below is how I check if a user already exist and send the error message,
exports.register = new LocalStrategy({
usernameField: 'email',
passReqToCallback: true
}, function(req, email, password, done,res) {
var newUser = new User({
email: email,
password: password,
name: req.body.fname
});
var searchUser = {
email: email
};
User.findOne(searchUser, function(err, user) {
if (err) return done(err);
console.log("err "+err);
if (user) {
return done(null, false, {
message: "email already exists"
});
}
newUser.save(function(err) {
console.log("newUser "+newUser);
done(null, newUser);
})
});
});
I use passport for authentication,
authRouter.post('/signup', passport.authenticate('local-register'),function(req, res) {
createSendToken(req.user, res);
});
The error message is not the one I pass to the front end. It shows unauthorized error, which is the default error. When I print the error message in the console in front end it shows,
Object {data: "Unauthorized", status: 401, config: Object, statusText: "Unauthorized"}
You are not saying what output you want in your front end, but I'm guessing you want to have data to be the message that you set in your LocalStrategy.
Here is one way to do that:
authRouter.post('/signup', function(req, res, next) {
passport.authenticate('local-register', function(err, user, info) {
if (err) { return next(err); }
if (!user) {
res.status(401);
res.end(info.message);
return;
}
createSendToken(req.user, res);
})(req, res, next);
});
You can take advantage of passReqToCallback: true in passport.
With this option enabled, req will be passed as the first argument to the verify callback.
Also by taking advantage of flash messages.
Here is basic example of how you use it,
// your strategy
passport.use(new LocalStrategy({
passReqToCallback: true
},
(req, username, password, done) => {
User.findOne({ username: username }, (err, user) => {
if (err) done(err)
if (!user) {
console.log('user does not exists')
return done(null, false, req.flash('message', 'User does not exist' ))
} else {
console.log('user exists')
return done(null, user, req.flash('message', 'User exist'))
}
})
}
))
// your GET login
router.get("/login", (req, res) => {
var message = req.flash('message')
res.render("pages/login", { message })
})
As mentioned in other answers you can manually check the info parameter and then return your own message based on that:
// ❌ MESSY ❌
authRouter.post('/signup', function(req, res, next) {
passport.authenticate('local-register', function(err, user, info) {
if(info.name === 'UserExistsError' ) {
return done(null, false, {
message: "Email already exists"
});
} else if (info.name === 'IncorrectUsernameError') {
return done(null, false, {
message: "Email does not exist"
});
} else if(....
But A MUCH cleaner way is to just specify custom error messages when you create the Account Mongoose model:
var Account = new Schema({
...
});
var options = {
errorMessages: {
UserExistsError: 'Email already exists',
IncorrectUsernameError: 'Email does not exist',
IncorrectPasswordError: ...
}
};
Account.plugin(passportLocalMongoose, options);
Then in your signup route you can simply return the info.message to the user.
// ✨ CLEAN ✨
authRouter.post('/signup', function(req, res, next) {
passport.authenticate('local-register', function(err, user, info) {
return done(null, false, {
message: info.message
});
});
});
If you are using passReqToCallback = true then you can send it just like
if (!isMatch) { return done({ message: 'Incorrect password' }); }
Related
I implemented a signup route below. It gets to "User saved..." but the request returns 404.
It doesn't seem to be executing the login strategy:
router.post("/signup", function(req, res, next) {
var email = req.body.email;
var password = req.body.password;
User.findOne({ email: email }, function(err, user) {
if (err) { return next(err); }
if (user) {
return res.status(409).send({message: "Duplicate user - already registered."});
}
var newUser = new User({
email: email,
password: password
});
newUser.save(next);
console.log("User saved...");
});
},
passport.authenticate("login"),
function(req, res) {
return res.status(200).send({
message: "Signup successful",
user: req.user
});
}
);
My Passport login strategy looks like this:
passport.use("login", new LocalStrategy(async (email, password, done) => {
console.log("login...");
User.findOne({ email: email }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: "No user has that email!" });
}
user.checkPassword(password, function(err, isMatch) {
console.log("Checked password...");
console.log("Error? Match?");
console.log(err);
console.log(isMatch);
if (err) { return done(err); }
if (isMatch) {
console.log("Returning done...");
return done(null, user, { message: 'Logged in Successfully' });
} else {
return done(null, false, { message: "Invalid password." });
}
});
});
}));
Here's what I see in the logs:
User saved...
POST /signup 400 181.122 ms - -
Passport is likely throwing the 400 error because the username/password fields are not set.
Passport expects username and password and what you are passing are the email and password. So you can modify the code and let passport's LocalStrategy use the email as the username.
You can set the username and password as follows:
passport.use("login", new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
},async (usernameField, passwordField, done) => {
console.log("login...");
User.findOne({ email: usernameField }, function (err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: "No user has that email!" });
}
user.checkPassword(passwordField, function (err, isMatch) {
console.log("Checked password...");
console.log("Error? Match?");
console.log(err);
console.log(isMatch);
if (err) { return done(err); }
if (isMatch) {
console.log("Returning done...");
return done(null, user, { message: 'Logged in Successfully' });
} else {
return done(null, false, { message: "Invalid password." });
}
});
});
}));
You can check the line which was throwing the error from passport's source code here
I in login form when the validation is failed i am able to show the validation error messages but i am not getting how to repopulate the login form.
what i want that the user should not enter his email address again in case of validation errors.
what i have done so far that i am trying to get email value as i am sending error back from passport.js using connect-flash.
my passport local strategy is.
passport.use(
"local.signin",
new LocalStrategy(
{
usernameField: "email",
passwordField: "password",
passReqToCallback: true
},
function(req, email, password, done) {
console.log("I am in passport");
req.checkBody("email", "invalid email")
.notEmpty()
.isEmail();
req.checkBody("password",'invalid password')
.notEmpty();
req.flash('email',email);
req.flash('password',password);
var errors = req.validationErrors();
if(errors){
var messages = [];
errors.forEach((error) => {
messages.push(error.msg);
});
console.log("errors: ", errors);
return done(null, false, req.flash("error", messages));
}
User.findOne({email:email}, (err, user) => {
if(err){
return done(err);
}
if(!user){
return done(null, false, {message: "No user found!"});
}
if(!user.validPassword(password)){
return done(null, false, {message: "Wrong Password"});
}
return done(null, user);
});
}
)
);
and the index function which the route redirect to after validation is failed
exports.index = function(req, res) {
var messages = req.flash("error");
console.log("email: ",req.flash('email'));
res.render("admin/index", {
layout: "loginSignup",
messages: messages,
hasErrors: messages.length > 0,
email: req.flash('email'),
password: req.flash('password')
});
};
Here when i console.log(); it shows empty array, email and password is not saved in flash and so i am not able to populate input field in view
i am not getting what is wrong with my code, when i can get error message why not email and password values.
or is there better way of repopulating the form in nodejs
Why not try something like this?
res.render("admin/index", {
layout: "loginSignup",
messages: messages,
hasErrors: messages.length > 0,
email: req.body('email') || '',
password: req.body('password') || ''
});
Note, this is assuming the route is mounted on an app.post() call and you are using the bodyparser middleware, if you are using app.get(), then change req.body for req.params in my example.
assign req.body to flash and then send back on validation error redirect like so
passport.use(
"local.signin",
new LocalStrategy(
{
usernameField: "email",
passwordField: "password",
passReqToCallback: true
},
function(req, email, password, done) {
req.checkBody("email", "invalid email")
.notEmpty()
.isEmail();
req.checkBody("password",'invalid password')
.notEmpty();
var form = req.flash("form",req.body);
var errors = req.validationErrors();
if(errors){
var messages = [];
errors.forEach((error) => {
messages.push(error.msg);
});
console.log("errors: ", errors);
return done(null, false, {
error: req.flash("error", messages),
form: form
});
}
User.findOne({email:email}, (err, user) => {
if(err){
return done(err);
}
if(!user){
return done(null, false, {message: "No user found!", form: form});
}
if(!user.validPassword(password)){
return done(null, false, {message: "Wrong Password", form: form});
}
return done(null, user);
});
}
)
);
and the on the redirect route get the req.body from form variable and send to your view in the following code as
exports.index = function(req, res) {
var messages = req.flash("error");
res.render("admin/index", {
layout: "loginSignup",
messages: messages,
hasErrors: messages.length > 0,
form : req.flash("form")
});
};
I'm new to node express and passport so I'm not sure what I'm missing. I'm trying to use a custom callback as all I want is a json response from my registration strategy. Currently I keep getting the following error "Missing credentials". I've been at it for a while now and I'm stuck.
Here is my controller:
app.post('/services/authentication/registration', function(req, res, next) {
console.log('before authentication')
passport.authenticate('local-registration', function(err, user, info) {
console.log('authentication callback');
if (err) { return res.send({'status':'err','message':err.message});}
if (!user) { return res.send({'status':'err','message':info.message});}
if (user!=false) { return res.send({'message':'registration successful'});}
})(req, res, next);
},
function(err, req, res, next) {
return res.send({'status':'err','message':err.message});
});
And my passport strategy:
passport.use('local-registration', new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField : 'email',
passwordField : 'password',
passReqToCallback : true
},
function(req, email, password, done) {
console.log('credentials passed to passport' + email + '' + password)
// asynchronous
// User.findOne wont fire unless data is sent back
process.nextTick(function() {
// 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
if (err)
return done(err);
// check to see if theres already a user with that email
if (user) {
return done(null, false, {message: 'User already exists'});
} else {
// if there is no user with that email
// create the user
var newUser = new User();
// set the user's local credentials
newUser.local.email = email;
newUser.local.password = newUser.generateHash(password);
// save the user
newUser.save(function(err) {
if (err)
throw err;
return done(null, newUser);
});
}
});
});
}));
I dont think you should send "req" as a parameter to the authentication function. take a look at this example from the passport docs:
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);
});
}
));
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 });
});
I'm using passportJS and I'm wanting to supply more than just req.body.username and req.body.password to my authentication strategy (passport-local).
I have 3 form fields: username, password, & foo
How do I go about accessing req.body.foo from my local strategy which looks like:
passport.use(new LocalStrategy(
{usernameField: 'email'},
function(email, password, done) {
User.findOne({ email: email }, function(err, user) {
if (err) { return done(err); }
if (!user) {
return done(null, false, { message: 'Unknown user' });
}
if (password != 1212) {
return done(null, false, { message: 'Invalid password' });
}
console.log('I just wanna see foo! ' + req.body.foo); // this fails!
return done(null, user, aToken);
});
}
));
I'm calling this inside my route (not as route middleware) like so:
app.post('/api/auth', function(req, res, next) {
passport.authenticate('local', {session:false}, function(err, user, token_record) {
if (err) { return next(err) }
res.json({access_token:token_record.access_token});
})(req, res, next);
});
There's a passReqToCallback option that you can enable, like so:
passport.use(new LocalStrategy(
{usernameField: 'email', passReqToCallback: true},
function(req, email, password, done) {
// now you can check req.body.foo
}
));
When, set req becomes the first argument to the verify callback, and you can inspect it as you wish.
In most common cases we need to provide 2 options for login
with email
with mobile
Its simple , we can take common filed username and query $or by two options , i posted following snippets,if some one have have same question .
We can also use 'passReqToCallback' is best option too , thanks #Jared Hanson
passport.use(new LocalStrategy({
usernameField: 'username', passReqToCallback: true
}, async (req, username, password, done) => {
try {
//find user with email or mobile
const user = await Users.findOne({ $or: [{ email: username }, { mobile: username }] });
//if not handle it
if (!user) {
return done(null, {
status: false,
message: "That e-mail address or mobile doesn't have an associated user account. Are you sure you've registered?"
});
}
//match password
const isMatch = await user.isValidPassword(password);
debugger
if (!isMatch) {
return done(null, {
status: false,
message: "Invalid username and password."
})
}
//otherwise return user
done(null, {
status: true,
data: user
});
} catch (error) {
done(error, {
status: false,
message: error
});
}
}));