Bad request while using passport in express - node.js

While integrate passport to my code's login form. Everything is working fine until i call passport.authenticate in the request, 400 Bad Request was returned. What i am doing wrong?
Strategy
passport.use('local.login',new LocalStrategy({
usernameField: 'Email',
passwordField: 'Password',
},function (email,done) {
Schema.Users.findOne({'Email': email},function (err,user) {
if(err) {
return done(err);
}
if(user==null) {
return done(null,false,{ message: 'Incorrect username.' })
}
if(user.Password!==Password) {
//console.log('Wrong password');
return done(null,false,{ message: 'Wrong password' })
}
return done(null,user);
})
}));
passport.serializeUser(function (user,done) {
done(null,user.id);
});
passport.deserializeUser(function (id,done) {
Schema.Users.findById(id,function (err,user) {
done(err,user);
})
});
Inside login.js
router.post('/x',passport.authenticate('local.signup',{
successRedirect: '/success',
failureFlash: '/failure'
}));
app.js
let login = require('./login.js');
app.use('/login',login);
HTML
<form action="http://localhost:8080/login/x" method="post">
<div class="row">
<div class="col s6">
<label for = "email"></label>
<input id = "Email" type="email" placeholder="Email" name="Email">
</div>
<div class="col s6">
<label for = "Password"></label>
<input id = "Password" type="Password" placeholder="Password" name="Password">
</div>
</div>
<button class="waves-effect waves-light btn" type="submit">Log In</button
</form>

Oh I see now, but I might be wrong. You have checking if password of user from db matches password sent from your front-end but you forgot to pass it inside this function, you have only an email there, so simply try to add password as an argument:
function (email, password, done) {
Schema.Users.findOne({'Email': email},function (err,user) {
if(err) {
return done(err);
}
if(user==null) {
return done(null,false,{ message: 'Incorrect username.' })
}
if(user.Password!==password) {
//console.log('Wrong password');
return done(null,false,{ message: 'Wrong password' })
}
return done(null,user);
})

Basically i didn't read the part of passport where it says by default it will accept only parameters named as username and password but i was trying to name it as Email and Password(notice the capital P)

Let's summarise it guys.
First, make sure if you're using any json parser in you express middleware. Body-parser looks depricated, so hust make sure you have this line in your code: app.use(express.json()).
Second, sometimes people get 400 from passport.authenticate because of credentials. So make sure your axios requests from frontend to backend has this {withCredentials: true} as a parameter.
And finally, make sure to use "username" and "password" spelled exactly like this in your userSchema, in your frontend input names, and in your strategy options. I used passport-local-mongoose, and looks like there's no need to configure local strategy, cause it use "username" and "password" by default.
Try any of this, 99% you'll be able to authenticate user and finally move on. Later, you'll find a way to specify "username" and "password" to be different, but for now I'm sure, your primary mission is to unstack)

Related

Post current React state to express server

I have tried a few different ways of doing this but no matter what I've tried I get the same result.
Once I start the server, the first time I post, it doesn't work. because the state hasn't updated (I think)? All of the following post requests afterwards work fine, along with the correct state. So I'm not so sure it's state. Here's my code
Register.js
export default class Register extends React.Component{
constructor(props){
super(props);
this.state = {
username: '',
password: ''
}
}
onChange = (e) => this.setState({[e.target.name]: e.target.value});
register = async () => {
await axios.post(config.API_URI + 'register', {
username: this.state.username,
password: this.state.password
});
}
render(){
return(
<div>
<form id='register-form' className='border'>
<div id='form-title'>Create account</div>
<div className='mb-3'>
<label className='form-label' htmlFor='username-label' name='username-label'>Username</label>
<input type='text' className='form-control' id='usernameInput' name='username' onChange={this.onChange}></input>
</div>
<div className='mb-3'>
<label className='form-label' htmlFor='password-label' name='password-label'>Password</label>
<input type='password' className='form-control' id='password-input' name='password' onChange={this.onChange}></input>
</div>
{/*}
<div className='mb-3'>
<label className='form-label' htmlFor='password-label-confirm' name='password-label-confirm'>Confirm Password</label>
<input type='password' className='form-control' id='password-input-confirm' name='passwordConfirm'></input>
</div>
{*/}
<div id='buttons'>
<button className='btn btn-primary' id='register-btn' onClick={this.register} disabled={this.state.username === '' & this.state.username === ''}>Register</button>
<Link className='btn btn-primary' to='/login' id='login-btn'>Login</Link>
</div>
</form>
</div>
)
}
}
server.js post request (currently just attempting to console.log my request instead of adding to my database). I have gotten it to save to the database just fine following the second post attempt.
...
app.post("/register", (req, res)=>{
try{
if(req.body){
console.log(req.body);
/*
User.find({username: req.body.username}, (err, data)=>{
if(data.length == 0){
let user = new User({
username: req.body.username,
password: req.body.password
});
user.save((err,data)=>{
if(err){
res.status(400).json({
errorMessage: err,
status: false
});
} else {
res.status(200).json({
status: true,
title: 'Registered Successfully'
});
}
});
}
});
*/
}
} catch(e){
res.status(400).json({
errorMessage: 'Something went wrong!',
status: false
})
}
});
I'm a beginner making my first full-stack application
Good night! I cannot comment because I'm too new to Stack Overflow, so I'll say the following as an answer:
I ran your code on my machine and it worked fine but the reload. When I clicked on the "Register" button, I read the current state value by adding console.log(this.state) just before await axios.post(...).
That worked since the first click. However, the page reloads each submition. You missed the event.preventDefault(). This function prevents the reload when submitting a form by clicking on a button inside it. This is the code:
register = async (event) => {
event.preventDefault();
await axios.post('http://localhost:3333/register', {
username: this.state.username,
password: this.state.password
});
}
Then, I ran a Node API with your code. It also console.log(req.body) worked perfectly fine as well.
Therefore, I don't have any other ideas of what could be causing the issue on your machine besides the missing event.preventDefault().
P.S.:
Some features that helps while debugging JavaScript:
Use the command debugger; on your Front-End code. For example, inside the register method. This allows you to stop the execution of the code and inspect all the variables available and their current values. This may help you.
While debugging don't forget to put a lot of console.log on your code in specific parts that could be related to your bug. This helps you understand what's going on.

How to get api's response when posting data?

I'm doing a React web site, with a node.JS REST Api, and actually there is something that I don't know how to do.
When I'm logging in, my React form make a POST request to my Api (considering it's a School Project so I'm working locally, my React app is on port 3000 and my Api is on port 8080), and when I'm submiting, I'm redirected on the response of my Api.
I'd like to be redirected to a page of my React App and receive the response of my Api (when I'm using res.redirect() I have the React page but not the Api response).
Did somebody know how to do it ?
Here is my React Form Component:
class Form extends Component{
constructor(){
super()
this.location = window.location.href
}
render(){
return(
<form action="http://localhost:8080/login" method="post">
<label for="identifier">Mail:</label>
<br/>
<input type="email" id="identifier" name="identifier"/>
<br/><br/>
<label for="password">Password:</label>
<br/>
<input type="password" id="password" name="password"/>
<input id="locator" name="locator" value={this.location} type="hidden"/>
<br/><br/><br/>
<button type="submit">Se connecter</button>
</form>
)
}
}
And here is my Api login route :
app.post('/login', (req, res) => {
console.log(req.body.identifier);
console.log(req.body.password);
client.connect().then(() => {
let newUser = { identifier: req.body.identifier}
return client.db(`${process.env.MONGODB}`).collection(`${process.env.MONGOCOLLECTION}`)
.findOne(newUser)
.then(
result => {
if(result == null){
console.log("No user found");
return res.status(401).json({ error: 'Utilisateur non trouvé !' });
}else{
console.log("User "+result.name+" "+result.firstName+" found");
if(req.body.password !== cryptr.decrypt(result.password)){
console.log("Mot de passe incorrect !");
return res.status(401).json({ error: 'Mot de passe incorrect !' });
}else{
const token = jwt.sign({
id: result._id,
username: result.identifier
}, "my_secret", { expiresIn: '3 hours' })
console.log(token)
return res.json({ access_token: token })
}
}
},
err => res.status(500).json({ err }),
);
})
.then(() => {
console.log("--------------------------------");
})
res.redirect(req.body.locator)
})
You can use XHR requests to handle API calls to server and fetching back data to render in your React component.
Instead of using action in form,
use
<form onSubmit={this.handleSubmit}>
and define a method which handles the submit logic.
async handleSubmit(e) {
const reponse = await
fetch('http://localhost:8080/login', {
method: 'POST',
mode: 'cors', //as your server is on different origin, your server will also have to enable CORS
body: JSON.stringify(formData) //formData will have to be extracted from your input elements before sending to server
})
const data = response.json() //This is the data from your server which you'll get without the need of redirecing.
}
fetch is the default js library to handle XHR request.
There are others as well like axios.

Node Express Post 500 (Internal Server Error) jquery-3.4.1.min.js

I've been working on a node js/express js app for some time now. I have get's and post's that work from jquery $ajax calls.
For some reason I have one post "loginUser" that isn't getting into the user route post method
This was working previously but now has stopped, and as any programmer would say "has stoppped working for some reason".
I don't beleive anything was changed. I've tried using GET's, new POST's names, etc.
Every time it only provides a 500 error without any information in the error object.
Loginpage.ejs Form:
<form class="form-horizontal" id="loginUserForm" >
<div class="controls">
<strong>Email</strong><br/><input type="email" class="form-control custom-input-width" id="login-email" value="adrian#adriannadeau.com" required>
</div>
<div class="controls">
<strong>Password</strong><br/><input type="password" class="form-control custom-input-width" minlength="6" id="login-password" value="Asialouie!123" required>
</div>
<div class="control-group">
<div class="controls text-center">
<br/>
<input type="submit" class="btn btn-green" id="button-signup" value="Sign in">
<br/><p class="aligncenter"><br/>
Don't have an account? <a href="/register" >Sign up</a>
<br/>Forgot password? Reset
</p>
</div>
</form>
JQuery Ajax code:
$("#loginUserForm").submit(function(event) {
// Prevent the form from submitting via the browser.
event.preventDefault();
try{
const email = $("#login-email").val();
const password = $("#login-password").val();
console.log("email: "+email);
console.log("pass: "+password);
var data = {};
data.email =email;
data.message =password;
$.ajax({
url: '/users/loginUser',
type: 'POST',
data: JSON.stringify(data),
contentType: 'application/json',
/**
* A function to be called if the request succeeds.
*/
success: function(data, textStatus, jqXHR) {
window.location.assign("/profile/?id="+data._id);
},
error : function(error) {
//alert(error.name + ': ' + error.message);
console.log(error);
$(".alert-signin").show();
}
});
}
catch(err) {
console.log("err: "+err.message);
}
});
User Route Method:
router.post('/loginUser', function(req, res) {
console.log("in user login");
});
This route Post works:
router.post('/',function(req,res){
logger.info("email: "+req.body.email);
User.findOne({ email:req.body.email}, function (error, user) {
if(user){
console.log("user exists");
const error = new Error('User email account already exists.');
res.status(410);
res.send(JSON.stringify(error.message));
}
else{
//save user
var pwd = req.body.password;
bcrypt.genSalt(10, function(err, salt) {
if (err) {
logger.error("BCrype issue");
const error = new Error("Unable to register, please try again.");
//throw new Error('User email account already exists.');
res.status(420);
res.send(JSON.stringify(error.message));
} else {
//console.log('Salt: ' + salt);
bcrypt.hash(pwd, salt, function (err, hash) {
if (err) {
logger.error("ERROR! users bcrypt");
const error = new Error("Unable to register, please try again.");
res.status(420);
res.send(JSON.stringify(error.message));
} else {
var user = new User({
firstname:req.body.firstname,
lastname :req.body.lastname,
email :req.body.email,
password : hash,
public:1,
admin: false,
bio : "",
location : "",
avatar: "",
url: "",
activated:"n"
});
user.save(function (error, user) {
if (error){
res.send(error.message);
}
res.send(user)
});
}
});
}
});
}
});
});
I expect the route to take the login information and get into the LoginUser route and output "in user login"
Here is the console output when the login form is posted:
POST http://localhost:3000/users/loginUser 500 (Internal Server Error) jquery-3.4.1.min.js:2
I don't understand why it's showing jquery-3.4.1.min.js:2 in the console for the 500 error.
Any help would be greatly appreciated.
I think you should try the followings:
You should put method attribute in the form tag
If you have the following statement in your server code, remove it or comment it down.
app.use(bodyParser.json());
If you have any attributes in the script tag in which jQuery is written, then remove them.
What status code 500 is normally there is some error in the server-side code ( in the root ). Double-check the route code.
These all suggestions are what I did with my code when I faced the 500 error.

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.

NodeJS not able to get token value from req.params.token

app.post('/reset/:token', function(req, res) {
async.waterfall([
function(done) {
User.findOne({ 'local.resetPasswordToken' : req.params.token, 'local.resetPasswordExpires' : { $gt: Date.now() } }, function(err, user) {
if (!user) {
req.flash('resetMessage', req.params.token);
return res.redirect('back');
}
], function(err) {
res.redirect('/');
});
});
app.get('/reset/:token', function(req, res) {
User.findOne({ 'local.resetPasswordToken': req.params.token, 'local.resetPasswordExpires' : { $gt: Date.now() } }, function(err, user) {
if (!user) {
req.flash('forgotMessage', req.params.token );
return res.redirect('/forgot');
}
res.render('reset.ejs', { user: req.user, message: req.flash('resetMessage') });
});
});
<!--Reset.ejs page ResetPassword FORM -->
<form action="/reset/:token" method="post">
<div class="form-group">
<label>New Password</label>
<input type="text" class="form-control" name="newpassword">
</div>
<div class="form-group">
<label>Confirm Password</label>
<input type="text" class="form-control" name="confirmpassword">
</div>
<button type="submit" class="btn btn-warning btn-lg">Reset</button>
</form>
I able to get the token with req.params.token for the "post" after clicking
http://localhost:8080/reset/fed831abf73150c96f6a3e392b5cbdcaccdeb9bd
Later when I submit through the reset.ejs for the "get" I couldn't retrieved any token value with req.params.token.
Any solution to it?
I imagine that the original code for this might have come from http://sahatyalkabov.com/how-to-implement-password-reset-in-nodejs/. In this tut the jade templating engine is used and if you look at the reset.jade you will see that it starts with
form(method='POST')
but no action is defined. I don't really know jade but in your example you are using ejs and in your code you are setting the action to
form action="/reset/:token" method="post"
and as everybody has pointed out the route that you post to is exactly /reset/:token. So req.params will be :token and the reset will fail. What you need to do is post the url exactly as it appears in the get request. If you read
Is it a good practice to use an empty URL for a HTML form's action attribute? (action="")
you can see that you can amend your reset.ejs page code to read
form action="" method="post"
Now the post should have an action equal to the get url with the token in place and the reset should occur.
you need another form with method='get' and action='reset/' + tokenvar. Also your async waterfall does not call done() so will not call the redirect if the user exists

Resources