I am new to NODEJS / express , and I was following a tutorial to build a small MERN app.
In the initial steps, while setting up a POST route, when sending a POST request with Postman, I am getting this Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
I´ve reading about it, and I somehow understand is because I am calling res.json two times in the same response.
My router is this :
router.post('/login',
async (req, res) => {
try {
const user = await userModel.findOne({email:req.body.email});
!user && res.status(404).send("user not found");
const correctPassword = await bcrypt.compare(req.body.password, user.password);
!correctPassword && res.status(400).json('incorrect password')
res.status(200).json(user); // if not invalid return user
} catch (err) {
console.log(err)
}
})
I tried different solutions i found here (IF & if Else statements, using return ...) without any success.
This very same code (with different variable names) is working flawless in mentioned tutorial.
Any idea how could I solve it?
Thanks in advance.
EDIT : I add the resto of my user Route for completion
´´´
import express from 'express';
const router = express.Router();
import bcrypt from 'bcrypt';
import userModel from '../models/userModel.js'
router.post('/register',
async (req, res) => {
try {
// hash password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(req.body.password, salt)
// Create New User
const newUser = new userModel({
userName: req.body.userName,
email: req.body.email,
password: hashedPassword,
});
// Save New User and return response
const user = await newUser.save();
res.status(200).json(user);
} catch(err){
console.log(err)
}
});
Likely user is falsy or correctPassword evaluates to false, which will send a response of "user not found" or "incorrect password".
However, it continues to execute the rest of your function and eventually executes this line
res.status(200).json(user);
This line will cause the error you've mentioned, as you have already sent a response to the client previously and cannot send another response in the same request-response-cycle.
This should fix the issue:
router.post("/login", async (req, res) => {
try {
const user = await userModel.findOne({ email: req.body.email });
if (!user) {
return res.status(404).send("user not found");
}
const correctPassword = await bcrypt.compare(req.body.password, user.password);
if (!correctPassword) {
return res.status(400).json("incorrect password");
}
return res.status(200).json(user); // if not invalid return user
} catch (err) {
console.log(err);
return res.status(500).send(err.message);
}
});
I'd recommend using ifs here, as it makes the code easier to read. I'd also recommend using return anytime you send a response as it will avoid the issue you ran into.
Related
const router = require("express").Router();
const user = require("../models/user");
const cryptoJs = require("crypto-js");
const dotenv = require("dotenv").config();
router.post("/register", async (req, res) => {
const newUser = new user({
username: req.body.username,
password: cryptoJs.AES.encrypt(req.body.password, process.env.pass),
});
try {
const savedUser = await newUser.save();
res.status(201).json(savedUser);
} catch (error) {
res.status(500).json(error);
}
});
router.post("/login", async (req, res) => {
try {
const oneUser = await user.findOne({ username: req.body.username });
if (!oneUser) {
res.status(401).json("Wrong credentials");
}
const hp = cryptoJs.AES.decrypt(oneUser.password, process.env.pass);
const password = hp.toString(cryptoJs.enc.Utf8);
if (password !== req.body.password) {
res.status(401).json("Wrong credentials");
}
res.status(200).json(oneUser);
} catch (error) {
res.sendStatus(500).json(error);
}
});
module.exports = router;
//so, there is the code! everything works fine up to the /login section. when I input the right username and password, it gets me the matching user from the database, but when I input the wrong username and the right password immediately after, it says "wrong credentials which is also fine. But when I input the wrong password after all the previous inputs, it brings this error " Cannot set headers after they are sent to the cliententer code here"
The set header error when will display that you send/return two "res" so use you have to use if-else not if
So the problem is that you send a response to the client, while you already sent a response to the client. When the password is different, you send "Wrong Credentials", but the script will also try to send the oneUser Mongo Object.
To get rid of that, either use an if .. else .. like #Evan proposed, either return the response so you're sure that the script stop there.
The "if/else" solution
if (password !== req.body.password) {
res.status(401).json("Wrong credentials");
}
else {
res.status(200).json(oneUser); // will be sent if the condition before is not completed
}
The "return" solution
if (password !== req.body.password) {
return res.status(401).json("Wrong credentials"); // if the password is different, this will stop the script here
}
res.status(200).json(oneUser);
its better you improve youre block condition like
if (condition){
// do something
}
else {
//do something else
}
OR you can return youre response . it means that when you want to send response return something and exit from the function .
this solution in your code is
router.post("/register", async (req, res) => {
const newUser = new user({
username: req.body.username,
password: cryptoJs.AES.encrypt(req.body.password, process.env.pass),
});
try {
const savedUser = await newUser.save();
return res.status(201).json(savedUser);
} catch (error) {
return res.status(500).json(error);
}
});
router.post("/login", async (req, res) => {
try {
const oneUser = await user.findOne({ username: req.body.username });
if (!oneUser) {
return res.status(401).json("Wrong credentials");
}
const hp = cryptoJs.AES.decrypt(oneUser.password, process.env.pass);
const password = hp.toString(cryptoJs.enc.Utf8);
if (password !== req.body.password) {
return res.status(401).json("Wrong credentials");
}
return res.status(200).json(oneUser);
} catch (error) {
return res.sendStatus(500).json(error);
}
});
I'm creating an authentication system using Node and Mongoose. I have a login user function here:
export const loginUser = async (req, res) => {
try {
const email = req.body.email;
const password = req.body.password;
//const workingUser = await User.findById("xxxxxxxxxxxx");
console.log(await User.findByCredentials(email, password));
const user = await User.findbyCredentials(email, password);
console.log(user);
if (!user) {
return res.status(401).json({ error: "Login failed! Check authentication credentials." });
}
const token = await user.generateAuthToken();
res.status(201).json({ user, token });
} catch (error) {
res.status(400).json({ error: error });
}
};
I always get a 400 error. Console logging 'user' shows nothing. When I substitute my 'findByCredentials' function for the commented out 'findById', the code works perfectly. Also, where I console log 'await User.findByCredentials(email, password)' the user I want is console logged, which makes me think the findByCredentials code is implemented correctly.
Here is the code for that:
// this method searches for a user by email and password
userSchema.statics.findByCredentials = async (email, password) => {
console.log(email);
const user = await User.findOne({ email });
if (!user) {
throw new Error({ error: "Invalid login details" });
}
const isPasswordMatch = await bcrypt.compare(password, user.password);
if (!isPasswordMatch) {
throw new Error({ error: "Invalid login details"});
}
return user;
}
const User = mongoose.model("User", userSchema);
export default User;
Not exactly sure what I'm doing wrong. Thank you
There's a typo in your code (in second line)
const user = await User.findByCredentials(email, password);
findByCredentials not findbyCredentials. See the capital B
Okay i am fairly new to node js and i am learning user authentication. I keep getting the 'Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client' error. can someone just tell me what is wrong with my code and how to fix it?. When i test it in postman, The register route works, its the login route that gives me this problem. Here is my code:
const User = require('../models/User')
const CryptoJS = require("crypto-js");
const jwt = require("jsonwebtoken");
const {BadRequestError, UnauthenticatedError} = require('../errors')
const Register = async (req, res)=>{
const newUser = new User({
username: req.body.username,
email: req.body.email,
password: CryptoJS.AES.encrypt(req.body.password, process.env.pass_secret ).toString(),
});
if(newUser){
const savedUser = await newUser.save();
res.status(201).json(savedUser);
}
}
const Login = async (req, res) =>{
const {username} = req.body
//checking if both the email and password are provided
if(!username){
throw new BadRequestError('please provide a username and password')
}
//finding a user with the email, if the user doesnt exist, return an error
const user = await User.findOne({username: req.body.username});
if(!user){
throw new UnauthenticatedError('Invalid username or password')
}
//checking if the passwords match
const hashedPassword = CryptoJS.AES.decrypt( user.password, process.env.pass_secret);
const originalPassword = hashedPassword.toString(CryptoJS.enc.Utf8);
if(originalPassword !== req.body.password){
throw new UnauthenticatedError('Invalid email or password')
}
const accessToken = jwt.sign( { id: user._id, isAdmin: user.isAdmin}, process.env.jwt_secret, {expiresIn:"30d"});
const { password, ...others } = user._doc;
res.status(200).json({...others, accessToken});
}
module.exports = {Register, Login}
Wherever you have this:
if(newUser){
const savedUser = await newUser.save();
res.status(201).json(savedUser);
}
You need to change it to:
if(newUser){
const savedUser = await newUser.save();
res.status(201).json(savedUser);
return;
}
You don't want the code to continue after you've done
res.status(201).json(savedUser);
because it will then try to send another response.
In addition, everywhere you have this:
if (err) throw err;
inside an async callback, you need to replace that with something that actually sends an error response such as:
if (err) {
console.log(err);
res.sendStatus(500);
return;
}
I am quite new to Node.js / Express and development of web apps. I try to do a simple user registration where I hash the password with bcrypt before saving the hash to mongodb. The login form, which should allow a user to login, does subsequently lookup a user in the db and then compares the two passwords.
Certain routes in my web app I do want to protect so that only authenticated user have access to them. So when successfully login in I do send a Json Web Token (jwt) along the response header which should then be used - when redirected to the protected '/lobby' route - to authenticate the user and allow him / her to proceed to that route.
However, I always get the following error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
So it looks like it already sends back a response to the client before trying to set the header which of course is then not possible anymore.
I would highly appreciate your help here!
I do use the following code:
Register function
async function register(req, res) {
//Check with user already exists
const emailExists = await User.findOne({email: req.body.email});
if(emailExists) {
return res.status(400).send('User already exists!');
};
//Hash the password and create new user from request data
bcrypt.hash(req.body.password, 10, async function (err, hashedPass){
if(err){
res.json({
error: err
});
}
let user = new User({
name: req.body.name,
email: req.body.email,
username: req.body.username,
password: hashedPass,
password2: hashedPass
});
try {
await user.save();
}catch (err) {
res.status(400).send(err);
};
});
res.render('index');
};
Login function
async function login(req, res) {
const user = await User.findOne({email: req.body.email});
if(!user) {
return res.status(400).json({message: 'User not found!'}).render('index');
};
bcrypt.compare(req.body.password, user.password).then((result)=> {
if(result){
const token = jwt.sign({_id: user._id}, process.env.TOKEN_SECRET);
res.setHeader('auth-token', token.toString());
res.redirect('/lobby');
}else {
return res.status(400).json({message: 'Passwords do not match!'}).render('index');
}
}).catch((err)=> {
console.log(err);
});
};
As a middleware to the '/lobby' route (i.e. when someone does a get request to '/lobby') I use a "verifyToken" function which should ensure correct authentication of the user via jwt.
verifyToken function
const jwt = require('jsonwebtoken');
module.exports = function(req, res, next) {
console.log('verify function started');
const token = req.header('auth-token');
console.log(token);
if(!token) {
res.status(401).json({
message: 'Access denied!'
});
};
try {
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
req.user = verified;
next();
}catch (err) {
res.status(400).json({
message: 'Invalid token!'
});
};
};
As said, I would very much appreciate your help here! I assume the problem is much simpler than I think it is :-).
Cheers
You forgot to return the response in few cases. So it continues to execute other code aswell, that's where server trying to send the response again, which is why you're getting that error.
Change your response like the following.
verifyToken function
const jwt = require('jsonwebtoken');
module.exports = function(req, res, next) {
console.log('verify function started');
const token = req.header('auth-token');
console.log(token);
if(!token) {
return res.status(401).json({ // <-- here you need to `return`
message: 'Access denied!'
});
};
try {
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
req.user = verified;
next();
}catch (err) {
return res.status(400).json({
message: 'Invalid token!'
});
};
};
Register function
async function register(req, res) {
//Check with user already exists
const emailExists = await User.findOne({email: req.body.email});
if(emailExists) {
return res.status(400).send('User already exists!');
};
//Hash the password and create new user from request data
bcrypt.hash(req.body.password, 10, async function (err, hashedPass){
if(err) {
return res.json({ // <-- here as well
error: err
});
}
let user = new User({
name: req.body.name,
email: req.body.email,
username: req.body.username,
password: hashedPass,
password2: hashedPass
});
try {
await user.save();
return res.render('index'); // <-- assuming this is your success response
}catch (err) {
return res.status(400).send(err); <-- here too
};
});
};
Looks like in the Login function the header gets set. I can see this via console.log(res.header('auth-token'));. Subsequently the redirect to "/lobby" gets called because the verifyToken function does start.
However, in the verifyToken function the respective header is then undefined. Because I always also get a 'Access denied!' message.
As said, I do call the verifyToken function as middleware when doing a get request to the /lobby route. The route for '/lobby' looks as follows:
const express = require('express');
const router = express.Router();
const lobbyCtrl = require('../controllers/lobby');
const verify = require('./verifyToken');
router.get('/', verify, lobbyCtrl.display);
module.exports = router;
I have created a Node.js API and am making requests to it using Next.js
Here is my Node.js controller. I am using express validator for validation.
If I fill in the form correctly, it works and the data is saved in mongo as expected. However, I want to send the validation errors back to the client when the form isn't filled in correctly. If I look in console, I can see the errors in the network tab.
exports.register = async (req, res) => {
// check if user exists in the database already
const emailExists = await User.findOne({ email: req.body.email });
if (emailExists) return res.status(400).send("Email already exists");
// hash password
const salt = await bcrypt.genSalt(10);
// hash the password with a salt
const passwordhash = await bcrypt.hash(req.body.password, salt);
// create new user
var user = new User({
name: req.body.name,
email: req.body.email,
password: passwordhash
});
try {
user = await user.save();
res.send({ user: user._id });
} catch {
res.status(400).send(err);
}
};
In Next.js, here is the code for making the http request
handleSubmit = event => {
const { name, email, password } = this.state;
event.preventDefault();
const user = {
name,
email,
password
};
try {
register(user);
} catch (ex) {
console.log(ex);
}
};
export const register = async user => {
const data = await http.post("http://localhost:8000/api/user/register", user);
console.log(data);
return data;
};
In console all I see is the below. So the console.log I am doing in the catch isn't working.
POST http://localhost:8000/api/user/register 422 (Unprocessable Entity)
Uncaught (in promise) Error: Request failed with status code 422
at createError (createError.js:16)
at settle (settle.js:17)
at XMLHttpRequest.handleLoad (xhr.js:59)
That's because the catch statement isn't being run because the function isn't throwing an exception by itself. You should add the error handling inside the function like this:
try {
register(user);
} catch (ex) {
console.log(ex);
}
};
export const register = async user => {
const data = await http.post("http://localhost:8000/api/user/register", user).catch((e) {
throw new Error(e);
});
console.log(data);
return data;
};
I managed to get it working like this:
try {
const response = await register(user);
console.log(response);
} catch (ex) {
if (ex.response && ex.response.status === 422) {
const errors = ex.response.data.errors;
this.setState({ errors });
}
}