I have the below code where I am trying to validate a user with its credentials from Mongo DB :
{
validate: async function (email, password, res) {
console.log('Inside validate method');
try {
var dbUserObj = await User.findOne({ email: email }, (err, user) => {
console.log('Inside validate method111111');
if (err) {
return res.status(500).send('Error on the server.');
}
console.log('Inside validate method 22222');
if (!user) {
return res.status(404).send('No user found.');
}
console.log('Inside validate method33333');
var passwordIsValid = bcrypt.compareSync(password, user.password);
console.log('Is Valid Password :: ' + passwordIsValid);
if (!passwordIsValid) {
return res.status(401).send({
auth: false,
token: null
});
}
});
} catch (e) {
}
console.log('DDDDBBBB USSSERRRR :::' + dbUserObj);
return dbUserObj;
}
}
The below code calls the validate method :
var auth = {
login: function(req, res,next) {
console.log('Inside login');
var email = req.body.email || '';
var password=req.body.password || '';
console.log('Before validate user');
// Fire a query to your DB and check if the credentials are valid
var dbUserObj = auth.validate(email,password,res);
if (!dbUserObj) { // If authentication fails, we send a 401 back
res.status(401);
res.json({
"status": 401,
"message": "Invalid credentials"
});
return;
}
if (dbUserObj) {
res.send(genToken(dbUserObj));
}
}
Whenever there is a condition when the password is incorrect i am getting the error :
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
Cannot really figure out the issue.
The route that calls your validate() needs to accept the next callback parameter from express, otherwise the framework assumes that when the asynchronous function returns (which occurs at the first await expression), it has completed all its work and at that point it continues down its routes to the default error handling which sends a 404 before your database query resumes async control flow in validate.
When your route handler accepts the next parameter, it indicates to express that the route will handle asynchronously, and you can do 1 of 3 things:
Don't call next() if you already send a response (which you always do in this case).
Call next() with no arguments if you don't send a response and want to delegate the response handling to the remaining routes.
Call next(error) if you want to delegate the response handling to remaining middleware which will handle the error reporting and response for you.
Related
I'm working on a Node backend that uses MongoDB as its database. When I should send my JWT token within the response header, I get an error that the headers cannot be set after they are sent to the client. Here is my POST request:
api.post("/account/create", async (req, res) => {
// Hash the password using bcrypt
const hashedPassword = await bcrypt.hash(req.body.password, 10);
// Store new login credentials
const user = {
username: req.body.username,
password: hashedPassword
};
// Create a new JWT token
const token = jwt.sign({ username: req.body.username }, secret);
// Search for a matching username from the database
await logincollection.findOne(user, (err, result) => {
// If username was not found, add the credentials to the database
if (result == null) {
// Insert the credentials to the login credentials collection
logincollection.insertOne(user, (err, result) => {
// If an error occurred, return code 404 to the client
if (err) {
res.status(404).send();
}
})
// Create personal collection for the user
userdb.createCollection(JSON.stringify(user.username), (err, result) => {
// If an error occurred, return code 404 to the client
if (err) {
res.status(404).send();
}
})
// Return code 200 (success)
res.status(200).send({ auth: true, token: token });
} else {
// If username was found, return code 400 to the client
res.status(400).send();
}
})
})
When I try to get the token value in another POST request, it returns undefined:
api.post("/account/login", async (req, res) => {
// User object
const user = {
username: req.body.username,
password: req.body.password
};
const token = req.headers["token"];
console.log(token);
// Get username as a string
const username = JSON.stringify(user.username);
// Get hashed password from the collection
const hashedPassword = await logincollection.findOne({ username: req.body.username });
console.log(hashedPassword);
// Search for matching login credentials
await logincollection.find(user, (err, result) => {
// If no token was given
if (!token) {
// Return code 401 to the client
res.status(401).send();
}
// Verify the given JWT token
jwt.verify(token, secret, (err, decoded) => {
// If verification failed
if (err) {
// Return code 500 to the client
res.status(500).send();
}
// Return code 200 and decoded token to the client
res.status(200).send(decoded);
})
// Use bcrypt to compare the passwords and authenticate login
bcrypt.compare(req.body.password, hashedPassword).then(match => {
// If the credentials match
if (match) {
// Return the result as an object
const sendObject = {
username: result.username,
password: result.password
};
// Return code 200 to the client
res.status(200).send(sendObject);
// Log to console when user logs in
console.log("User " + username + " logged in");
// If the credentials do not match
} else {
// Return code 404 to the client
res.status(404).send();
}
// If comparing fails
}).catch(error => {
// Return coe 500 to the client
res.status(500).send();
})
})
})
I'm pretty sure the solution is something incredibly simple, but I just can't seem to solve this, although I've done a lot of research already.
Here's the response returned to the client from the /account/create request:
{
"auth": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QyIiwiaWF0IjoxNjMzNzg3MjE2fQ.duo5R9wXpk2Gj-iPHFMaDgKK0p3h6WZf5vnXrZViePo"
}
EDIT: Turns out that the token does not go the header, but to the body. What do I need to do differently to get it passed to the header?
Okay, it looks like I had understood this wrong.
I thought that the token has to be always returned and received in a header. This is not the case. The token response from /account/create has to be in the body. The token has to be set to the header only, when triggering the /account/login request (authenticating the login).
Hopefully this might help someone having the same question in the future.
In the result == null case, you have three statements that send a response: two asynchronous res.status(404) statements and one synchronous res.status(200) statement. Only of them may be executed per request.
But with the current code, the status 200 statement is executed for every request, even if a failed database operation later leads to an additional status 404 statement, which then causes the observed error.
I am trying to understand how app.get() works in calling functions when trying to switch between web pages.
I've created a user-login page that assigns a token to the user and checks it in a function.
I use app.post('/login', login); to call the login function which sends the user object to the server. After creating the token I'm hoping to then render the next page in a function after checking the token. (See code below)
However, I don't really understand how app.get('/', checkToken, getProfilePage) is then called. As I don't think it ever gets called.
I've looked at some websites that explain about HTTP requests but I'm struggling to find out, how it all links together inside app.js.
App.js:
app.post('/login', login);
app.get('/', authorize.checkToken, getProfilePage);
function login(req, res, next) {
userService.login(req.body, (err, user) => {
if (err) {
res.redirect('error.ejs');
console.log(error.message);
}
console.log(user);
if (!user) {
res.status(400).json({ success: false, message: 'Username or
password is incorrect' });
}
else {
res.json(user);
}
})
}
The next login function assigns the token and is used above as middleware:
function login({ username, password }, callback) {
grabUsers((err, users) => {
let user = users.find(u => u.username === username && u.password
=== password);
if (user) {
const token = jwt.sign({ username: username }, config.secret,
{ expiresIn: '24h'
}
);
const { password, ...userWithoutPassword } = user;
user = {
...userWithoutPassword,
success: true,
message: 'Authentication successful!',
token: token
}
};
callback(null,user);
})
}
Inside authorize.js:
let jwt = require('jsonwebtoken');
const config = require('./config.js');
let checkToken = (req, res, next) => {
console.log("check token running...");
let token = req.headers['x-access-token'] ||
req.headers['authorization']; // Express headers are auto
converted to lowercase
if (token.startsWith('Bearer ')) {
// Remove Bearer from string
token = token.slice(7, token.length);
}
if (token) {
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
return res.json({
success: false,
message: 'Token is not valid'
});
} else {
req.decoded = decoded;
next();
}
});
} else {
return res.json({
success: false,
message: 'Auth token is not supplied'
});
}
};
module.exports = {
checkToken: checkToken }
getProfilePage function:
module.exports = {
getProfilePage: (req, res) => {
res.render('profile.ejs');
}
}
So my login form posts to /login and then once it has been verified I would like to check the token and then call getProfilePage. But how do I call the app.get() after the login data has been posted and authenticated?
You don't call a route from your app, what you need to do is redirect to it : res.redirect('/');
I believe there is a problem with how you try to authenticate a user. Seems to me you send a token in authorization header which is a common practice when you accessing API. Not sure how/when you generate the token and set this header though...
Anyway, this approach is good for authorization but not so good for authentication.
After successful /login request you should set the authentication cookie (user session). To simplify, you can just generate a JWT with userId encoded into it and use it as the value for this cookie (let's call it user-session).
Now after that each time user makes a request the cookie will be sent with it and you can decode the userId from this JWT. (Same thing, but now you'll take token from req.cookies['user-session'] instead of req.headers['authorization']).
But how do I call the app.get() after the login data has been posted and authenticated?
You can either navigate to this page from the client right after you receive successful /login response if you're using AJAX (i.e. window.location.replace('/')) or you can do res.redirect('/') instead of res.json(user) on successful login if you submit the HTML form without AJAX.
Redirect forces a browser to immediately make another request to the URL you specify and by that time you'll have user-session cookie set, i.e. you'll be able to retrieve userId and return correct profile page.
Error handling in NODE JS
this is my Login Function
router.post('/admin/login', function (req, res) {
User.findOne({ username: req.body.username }, function (err, user) {
if (err) return res.status(500).send('Error on the server.');
if (!user) return res.status(404).send('No user found.');
var passwordIsValid = bcrypt.compareSync(req.body.password, user.password);
if (!passwordIsValid) return res.status(401).send({ auth: false, token: null });
if (req.body.username && req.body.password) {
console.log("enter");
var token = jwt.sign({ id: user._id }, config.secret, {
expiresIn: 86400 // expires in 24 hours
});
res.status(200).send({ auth: true, token: token });
} else {
return res.status(500).send('Error on the server.');
//res.status(500).send("Check Username & Password");
}
});
});
if i forget to enter password the server will be crashed how to handle on this
You need to check to see if the password is being passed before you pass it into the compareSync function.
if (!req.body.password) {
// handle error
return res.status(401).send("Missing or invalid password")
}
If you're doing this you should also check if req.body.username is being provided in the post request. Alternatively for easier method, you can have a try catch wrapped around the query to handle other unexpected errors.
It's better to check in front-end (client side) and also to check for email and password in back-end , there is various libraries to do that , for example i use express-validator lib here is a simple check and of course you can read full docs https://express-validator.github.io/docs/
code sample :
const { check, validationResult, body } = require('express-validator/check');
router.post('/admin/login',[check('email').isEmail(),check('password').isLength({ min: 5 })], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
// the rest of your sing in code here
})
and you can check for name length also this is safer for your back-end always check for validation in front-end and back-end, never trust client side only , do your own validation in the back-end , i hope this answer your question
I was having the same issue with a very similar code.
I replaced this line:
(...)
if (!user) return res.status(404).send('No user found.');
For this:
User.findOne({username : req.body.username},function(err, user){
if(err){
return res.status(401).json({message : err})
};
/* Trying to repair error when username is not on DB */
if(user === null) {
return res.status(401).json({auth : false, token : null, message : "Not Authorised User"});
};
/* Replacement worked */
var isPasswordValid = bcrypt.compareSync(req.body.password, user.password);
Hope it works for you.
My server has a registration api that provides a token after registration, and a middleware that authenticates a user's token. I need to register an account to get the token to do something else with my server. However, the middleware blocks my network request because I don't have a token yet.
So how can I create my account and token in this case? Get pass the middleware with some tricks?
Middleware:
// Middleware to verify token, it will be called everytime a request is sent to API
api.use((req, res, next)=> {
var token = req.headers.token
if (token) {
jwt.verify(token, secret, (err, decoded)=> {
if (err) {
res.status(403).send({ success: false, message: "Failed to authenticate user." })
} else {
req.decoded = decoded
next()
}
})
} else {
res.status(403).send({ success: false, message: "No Token Provided." })
}
})
Signin:
// Sign In with email API
api.post('/signInWithEmail', (req, res)=> {
User.findOne({
email: req.body.email
}).select(userFields).exec((err, user)=> {
if(err) {
throw err
}
if (!user) {
res.send({ message: "User doesn't exist"});
} else if (user) {
var validPassword = user.comparePassword(req.body.password);
if (!validPassword) {
res.send({ message: "Invalid Password"});
} else {
var token = createToken(user);
res.json({
success: true,
message: "Login Successfully!",
token: token
})
}
}
})
})
Make a function to check tokens and expose your routes such that whenever you need to call an authenticated route then you'll be checking the token first and then you'll expose the route.
Sample Code
Let's say this is my check token function
function checkToken(req, res, next) {
var x = req.token; //This is just an example, please send token via header
if (x === token)
{
next();
}
else
{
res.redirect(/unauthorized); //here do whatever you want to do
}
}
Now let's use the function for routes.
app.post('/protectedroute', checkToken, routename.functionname);
app.post('/notprotected', routename.functionname);
It's your call if you'd like to have separate routes for different codes or else you can just call specific code block via keeping them in function etc. on the main file i.e. app.js or server.js, whatever you have chosen.
What actually we are doing here is - we are making a middleware of our own to expose our routes through a channel of code blocks or functions.
I am building an API using Restify and Mongoose for NodeJS. In the method below after finding the user and verifying their password, I am trying to save some login information before sending the response back to the user. The problem is the response will never return. If I place the response outside and after the save call, the data never gets persisted to MongoDB. Am I doing something wrong? And help would be great as I have been working on this for the past 2 days.
login: function(req, res, next) {
// Get the needed parameters
var email = req.params.email;
var password = req.params.password;
// If the params contain an email and password
if (email && password) {
// Find the user
findUserByEmail(email, function(err, user) {
if (err) {
res.send(new restify.InternalError());
return next();
}
// If we found a user
if (user) {
// Verify the password
user.verifyPassword(password, function(err, isMatch) {
if (err) {
res.send(new restify.InternalError());
return next();
}
// If it is a match
if (isMatch) {
// Update the login info for the user
user.loginCount++;
user.lastLoginAt = user.currentLoginAt;
user.currentLoginAt = moment.utc();
user.lastLoginIP = user.currentLoginIP;
user.currentLoginIP = req.connection.remoteAddress;
user.save(function (err) {
if (err) {
res.send(new restify.InternalError());
return next();
}
// NEVER RETURNS!!!!
// Send back the user
res.send(200, user);
return next();
});
}
else {
res.send(new restify.InvalidCredentialsError("Email and/or password are incorrect."));
return next();
}
});
}
else {
res.send(new restify.InvalidCredentialsError("Email and/or password are incorrect."));
return next();
}
});
}
else {
res.send(new restify.MissingParameterError());
return next();
}
},
One cause of this issue can be if you have a pre save hook which errors silently.
If you find your model as a .pre('save' () => {...}) function then double check this method is reached after you call save, and that it returns without errors.
Documentation on mongoose middleware can be found here