BAD REQUEST with passport.authenticate() - node.js

I'm a little new to using passportjs in authenticating users and i'm trying to register a user on a simple application i'm building and i keep getting this error "Bad Request" in the browser. The user does get created in the DB but the redirect doesn't work. Any help would be greatly appreciated. Sorry if this question has been asked before :)
I have attached some bits of my code.
User.register(new User({username : req.body.user_email}), req.body.user_password, function(err, user){
if(err) {
return res.render("register-user", { userDetails : user})
}
passport.authenticate("local")(req, res, function(){
res.redirect("/profile");
})
})
My user Schema is as follows:
const usersSchema = new mongoose.Schema({
username : String,
password : String
})

So i did a little more digging and i found the answer to my problem. It seems to be that passportjs requires the form inputs to have username and password as their name attributes before it will work.

From my observation, the user object argument was not complete, you didn't include the password path, check the fixed code below:
User.register(new User({
username : req.body.user_email,
password: req.body.user_password}),
function(err, user){
if(err) {
return res.render("register-user", { userDetails : user})
}
passport.authenticate("local")(req, res, function(){
res.redirect("/profile");
})
})

I had the same issue. The solution to this problem is making the form inputs have exactly username and password as the name attribute. The new user should also have this format
<input type="email" name="username">
<input type="password" name="password">
and in your js file
const user = new User({
username: req.body.username,
password: req.body.password
})

Related

Save additional field to mongodb using passport local strategy

I am new to web development.
I am following a Node, Express, Passport and MongoDB tutorial (there is a question about this specific tutorial on SO but I am unable to apply the solution to my problem.)
Passport provisions saving email and password. I want to add a displayName. I have added this attribute to my schema. Added a field for it in the signup form. Then tried to save it via the localStrategy when creating a new User. The email and password save successfully however displayName does not.
in user.js userSchema is defined as
var userSchema = mongoose.Schema({
local : {
email : String,
password : String,
displayName : String,
}
});
module.exports = mongoose.model('User', userSchema);
the sign up form is in signup.ejs
<form action="/signup" method="post">
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" name="displayName">
//I want to save this to local.displayName in userSchema
</div>
<div class="form-group">
<label>Email</label>
<input type="text" class="form-control" name="email">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" class="form-control" name="password">
</div>
<button type="submit" class="btn btn-warning btn-lg">Signup</button>
</form>
routes.js to process the signup form
app.post('/signup', passport.authenticate('local-signup', {
successRedirect : '/profile', // redirect to the secure profile section
failureRedirect : '/signup', // redirect back to the signup page if there is an error
failureFlash : true // allow flash messages
}));
passport.js
passport.use('local-signup', 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,displayName,email,password, done) {
//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, req.flash('signupMessage', 'That email is already taken.'));
} 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);
// i need help with this.
newUser.local.displayName = req.body.displayName;
//save the user
newUser.save(function(err) {
if(err)
throw err;
return done(null, newUser);
});
}
});
});
}));
I want to save the value entered for displayName in the form to local.displayName at the time of sign up.
I have tried:
newUser.local.displayName = req.body.displayName; did not work
also,
var displayName = req.body.displayName; did not work
I have tried the following solutions to no avail:
Using PassportJS, how does one pass additional form fields to the local authentication strategy?
how can i store other form fields with passport-local.js
Update or add fields in passport.js local-strategy?
EDIT: console.log output of req.body.displayName and newUser.local.displayName
Ok, turns out it was working to begin with. I use Robo 3T to look inside my database and refreshing the connection was not enough to reflect the change. After reestablishing a connection with the host I was able to see the updated db.

Why is passport.authenticate needed in a registration?

I'm learning Passport, using local strategy and Mongoose, through passport-local-mongoose plugin.
It makes sense to me in a /login for example, but I don't understand why passport.authenticate is needed in a registration, right after it having already taken place...
Can anyone explain me, please? Also, what if it fails?
The following code was taken from a tutorial, but I have found similar constructions all over.
router.post('/register', function(req, res) {
User.register(new User({ username : req.body.username }),
req.body.password, function(err, user) {
if (err) {
return res.status(500).json({err: err});
}
if (req.body.firstname) {
user.firstname = req.body.firstname;
}
if (req.body.lastname) {
user.lastname = req.body.lastname;
}
user.save(function(err,user) {
passport.authenticate('local')(req, res, function () {
return res.status(200).json({status: 'Registration Successful!'});
});
});
});
});
The password isn't used to authenticated the user inside this snipped.
Call User.register
Creates a new User Object with the username, and needs the password to do
some magic with it. Probably build a hash which gets added to your 'user' object.
Call user.save
Saves this 'user' object inside your Storage, if this is successful the user gets authenticated, and returns HTTP 200 which ends the registration process.
Error Handling isn't implemented in this save method, for that you will have an exception on error.
Hope this helps to understand, your snippet.
To use your /login method your new user can use his username and password which he gave to you during the registration process.

Creating a User and authenticating using passport.js

I am trying to save a User and Register it at the same time. I am having troubles doing both simultaneously.
My current Schema:
const userSchema = new mongoose.Schema({
username: String,
password: String,
firstName: String,
lastName: String,
player: {
type: Boolean,
default: true
}
});
userSchema.plugin(passportLocalMongoose);
module.exports = (User, "userSchema");
My current form:
<form action="/player" method="POST">
<input type="text" name="user[username]" placeholder="username">
<input type="password" name ="user[password]" placeholder="password">
<input type="text" name="user[firstName]" placeholder="First Name">
<input type="text" name="user[lastName]" placeholder="Last Name">
<button>Submit</button>
</form>
I am requiring in express, passport, passport-local, passport-local-mongoose, body-parser, mongoose, and my schemas.
My app.js after that is:
mongoose.connect(database);
app.use(bodyParser.urlencoded({extended: true}));
app.use(require("express-session")({
secret: "Generic Secret Password",
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(User.authenticate()));
passport.serializeUser(User.serialUser());
passport.deserializeUser(User.deserializeUser());
//route
app.post("/player", function(req, res){
var newUser = new User({ username: req.body.user.email });
User.register(newUser, req.body.user.password, function(err, user){
if(err){
console.log(err);
return res.render("new");
}
passport.authenticate("local")(req, res, function(){
res.redirect("/player");
});
});
The problem I am having is that when I submit, the User saves the salt, hash, username, and sets player to "true", but it doesn't save lastName or firstName and it follows with a bad request(400). I can do a User.Create and it saves everything (minus the hash and salt) and redirects fine. I can also set the form up so that "user[username]" and "user[password]" are "username" and "password" and get it to save everything but firstName and lastName and it will redirect properly without a 400 status. I have tried changing the default inputs for "usernameField" and "passwordField", but I have had no luck. I've tried to look at various SO posts and the closest I found was a similar suggestion to change the defaults, so maybe I am doing it wrong. I've also tried to redirect to different routes as well and still no luck.
This is the first project I've worked on that isn't a code-along and I've tried to be as specific as possible on everything and I've been struggling for a solid eight-hours. Thanks for any help!
You have to specify the different parameters
var newUser = new User({ username: req.body.user.email,
firstName: req.body.user.firstName,
lastName: req.body.user.lastName});
you have to specify first name and lastname here var newUser = new User({ username: req.body.user.email, firstname: req.body.user.firstname, lastname:req.body.user.lastname});

How to change passport to use email instead of username for authenticating?

I wanna use user's email and password for logging in.
But in passport you should use username.
I've tried many examples about how to do that. But non worked.
The real problem is when application wants to save the record into database.
Although I have change the code to look for email not username field, this is the final record saved to database:
'name': 'joseph',
'username' : 'example#gmail.com',
.
.
But I don't want the email to be save as username.
And when I change it with mongoose after user registration it would be correct but again the problem will come when app wants to authenticate user.
It will look for username field in database not email. Because user will provide email as the username, login will fail.
Code:
app.get("/register", function(req, res) {
var newUser = new User({
username : req.body.username,
email : req.body.email,
name: req.body.name
});
User.register(newUser, req.body.password, function(err, user) {
if (err) {
req.flash('error', "An error ocurred, please try again.");
return res.redirect('back');
} else {
passport.authenticate("local")(req, res, function() {
req.flash("success", "Welcome");
res.redirect("/admin");
});
}
});
});
This code register user the way I want, but the problem is about login:
router.post('/login', middleware.outLoggedIn, passport.authenticate("local", {
successRedirect: "/admin",
failureRedirect: "/login",
failureFlash: "Invalid username or password",
successFlash: "Welcome!"
}));
It always fails, because it will get user email provided in login form, then it'll compare it with the one in database. And since I have saved the user with email and username, it will compare the provided email in login form with username field in database. AND FAIL.
This is the user in databse:
{
"_id" : ObjectId("5901cc5c0256ed17b4d61960"),
"salt" : "---",
"hash" : "---",
"username" : "joseph320",
"email" : "example#gmail.com",
"name" : "joseph",
"__v" : 0
}
(Login form requires email and password)
I have also done this:
passport.use(new localStrategy({
usernameField: 'email'
}, User.authenticate()));
I really appreciate it if you can show me how to fix that.
Can you provide more of your code? Some context is important here, such as which npm modules you're using to read and write to your database.
** If you are using passport-local-mongoose
You said you are using mongoose, and it looks like you might be using passport-local-mongoose on top of that. If this is the case, you can look at the options for passport-local-mongoose. This also includes a usernameField option, which indicates where you want the "username" to be stored. The usage for that would look like this:
User.plugin(passportLocalMongoose, {usernameField: 'email'})

Validate PassportJS via Callback or Mongoose Model?

I am setting up passport for the sign up and login user authentication. Now I saw that I can validate the information via the callback, however I could also validate the information in the User model. For example, if I want to make the email field required, then I can do the following:
Validation with Passport
// Create the local sign up with Passport
passport.use('local-signup', new LocalStrategy({
usernameField : 'userEmail', // Email Field
passwordField : 'userPassword', // Password Field
passReqToCallback : true
},
function(req, email, password, done) {
// Find a user with this email
User.findOne({ "local.email": email }, function(err, user) {
if (err) throw err;
// If a user with this email already exists, continue with failureRedirect
if (user) {
return done(null);
} else {
// Otherwise create a new user with the email and generate a hashed password
User.create({
local: {
email: email
}
}, function(err, user) {
if (err) throw err;
user.local.password = user.generateHash(password);
// Return the user and finish! :)
return done(null, user);
});
}
});
};
}));
With the User Model
var userSchema = new Schema({
local: {
email: {
type: String,
unique: true // Make it unique! Handles entire validation
},
password: {
type: String
},
}
});
Which of them is recommended and why?
Why not both? If you use the second one only, it will be difficult to show the message
email address already exists
as you need to catch the duplicate key error index and then display the error message at signup. Instead of that you could check that if email address already exists which is more explicit and display the corresponding error message at signup at the same time using unique index will make sure that there are no duplicate entries for email address.

Resources