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.
Related
I am creating a blog using NodeJS and Express framework. To add extra security, I have separated the Sign Up & Login from the application. For this, I have created a REST API using Express in PORT 5000 (just for authentication of protected routes) and the actual application runs on 5001.
When user POSTS from the Login page in browser to ”http://www.example.com:5001” i.e., to my application. I authenticate the values from the REST API running on PORT 5000.
//First user posts login data into my application
router.post('/login', async (req, res) => {
try{
let payload = {
'username': req.body.username,
'password': req.body.password,
}
//sending the parameters for authentication to PORT 5000 (i.e., REST API)
const response = await axios.post('http://www.example.com:5000/login', payload);
if(response)
{
console.log(response.data);
newToken = {
token: response.data.token,
refreshToken: response.data.refreshToken
}
res.redirect('/');
}
}
catch(err){
console.log(err);
}
})
The REST API responds after receiving the request.
app.post('/login', async (req, res, next) => {
try{
let token, refreshToken;
await connectDB();
let result = await findOneUser(req);
if(result)
{
//Initialize JWT Token Signature
try {
//Creating jwt token
token = jwt.sign(
{ username: req.body.username },
"secretkeyappearshere",
{ expiresIn: "1h" }
);
refreshToken = jwt.sign({ username: req.body.username },
"some-secret-refresh-token-shit",
{ expiresIn: "3h" }
);
} catch (err) {
console.log(err);
const error = new Error("Error! Something went wrong.");
return next(error);
}
}
const response = {
"status": "Logged in",
"token": token,
"refreshToken": refreshToken,
}
res.send(response);
return next(false);
}
catch(err){
console.log(err);
tokenList[refreshToken] = response
res.status(200).json(response);
next(false);
}
});
Everything goes fine and I receive the token in my application.
But, problem starts when I resend the token. Suppose I have to authenticate a route (say) the settings route. I want users should again authenticate. So, I send back the token which I received during my first call to the REST API again, but now I don't receive anything back from the API to my application.
The first piece of code above has a variable called "newtoken", which I have set as global in my application.js file. I store the token that I received in the first call in this variable. Now, I use this to send another request to authenticate for the settings page/ route.
So, I again send this token in my next call like this
router.get('/settings', async (req, res) => {
let data = {
params: {
from: "2022-03-12",
to: "2022-03-13"
},
headers: {
"X-Auth-Token": newToken,
"content-type": "application/json"
}
};
try{
const call = await axios.post('http://www.example.com:5000/validate', data);
if(call){
console.log(call.data);
res.render('settings', { name: "Some test data" });
}
}
catch(err)
{
console.log(err);
}
});
As, you can see I am resending the token data with headers. Actually, I have tried all others ways of sending, but nothings works. I do not get any response from my next call. ANy help would be highly appreciated. Thanks in advance.
Your router will handle many requests (from different users) in parallel, but your global variable newToken exists only once, this cannot work.
Your router could either send the token to the client (browser) in a session cookie, or use express-session and store the token in req.session.token:
// newToken = {
// token: response.data.token,
// refreshToken: response.data.refreshToken
// }
req.session.token = response.data.token;
req.session.refreshToken = response.data.refreshToken;
I'm making my own small API, and I've coded out the following POST request to my MongoDB database:
api.post("/account/login", async (req, res) => {
const user = {
username: req.body.username,
password: req.body.password
};
const username = JSON.stringify(user.username);
const hashedPassword = await logincollection.find(user.username).toArray();
const hardCodedPassword = "$2b$10$q0iOBFTqqZ3vnp5oqDQUqejdS7UD/ayw4Q4qgi5hs1pfFI.xfipDS"
console.log(hashedPassword)
// Search for matching login credentials
logincollection.find(user, (err, result) => {
try {
if (bcrypt.compare(req.body.password, hardCodedPassword)) {
// Return credentials back to the client
const sendObject = {
username: result.username,
password: result.password
};
console.log(sendObject);
// Return code 200 (success) to the client
res.status(200).send(sendObject);
// Log to console when user logs in
console.log("User " + username + " logged in");
}
} catch(error) {
// If matching credentials were not found, return code 404 (wrong credentials) to the client
res.status(404).send()
}
})
})
I have set a valid hardcoded password for purely testing purposes. When this request is processed, console.log(sendObject) prints undefined results to the console and bcrypt returns true, no matter what password I put in. What's the problem here?
As #jonrsharpe said, bcrypt.compare returns a Promise not a value. You must use a callback function, Promise.then() or async/await to handle the asynchronous result.
// Search for matching login credentials
logincollection.find(user, (err, result) => {
bcrypt.compare(req.body.password, hardCodedPassword)
.then(match => {
if (match) {
// Return credentials back to the client
const sendObject = {
username: result.username,
password: result.password
};
console.log(sendObject);
// Return code 200 (success) to the client
res.status(200).send(sendObject);
// Log to console when user logs in
console.log("User " + username + " logged in");
} else {
// If matching credentials were not found, return code 404 (wrong credentials) to the client
res.status(404).send()
}
})
.catch(error => {
res.status(500).send()
})
})
I'm making a Node backend using express (also using bcrypt for password hashing and jsonwebtoken for authorization). My requests are in separate files and they all use module.exports. I have this login request that is in a file called login.js:
router.post("/account/login", async (req, res) => {
// User object
const user = {
username: req.body.username,
password: req.body.password
};
// Get user name only
const username = {
username: req.body.username
};
// Create a new JWT token
const token = jwt.sign(username, secret);
// Get username as a string
const usernameString = JSON.stringify(user.username);
// Get hashed password from the collection
const hashedPassword = await logincollection.findOne({ username: req.body.username });
// Search for matching login credentials
await logincollection.findOne(username, (err, result) => {
// If username was not found, return code 400
if (result == null) {
res.status(400).send();
}
// Proceed if username was found
else {
// If no token was given, return code 401
if (!token) {
res.status(401).send();
}
// Verify the given JWT token
jwt.verify(token, secret, (err, decoded) => {
// If verification failed, return code 500
if (err) {
res.status(500).send();
}
})
// Use bcrypt to compare the passwords and authenticate login
bcrypt.compare(req.body.password, hashedPassword.password).then(match => {
// If the credentials match
if (match) {
// Insert the token into a cookie
res.cookie("token", token, { httpOnly: true });
// Return code 200
res.status(200).send({ auth: true, token: token });
// Log to console when user logs in
console.log("User " + usernameString + " logged in");
}
// If the credentials do not match
else {
// Return code 404
res.status(400).send();
}
// If comparing fails
}).catch(error => {
// Return code 500
res.status(500).send();
})
}
})
})
Every user has their own collection in a MongoDB database that is created, when the account is created. These collections are named after the username, since usernames are unique here. In this load.js file I have a request that loads all the objects in the collection:
router.post("/note/load", async (req, res) => {
// User object
const username = {
username: req.body.username
};
// Get usercollection depending on username
const usercollection = userdb.collection(JSON.stringify(username));
// Load the notes
const loadNote = await usercollection.find().toArray();
// Return code 200 and sendObject
res.status(200).send(loadNote);
})
Obviously, the code above will result in an error, because the username is not given in the request. Is there a way to pass the username to any request, when needed? Or can I use JWT for this, and if so, how? I have tried storing usernames in a variable, but that doesn't work, since the variable gets overwritten, when two users are logged in at the same time.
PS. The title is poor, because I don't know how to put this shortly. If you have any suggestions or corrections regarding the question or the title, please comment below.
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.
strong textI am building node.js + mongodb rest api. I use jwt user auth and I have a problem. I need to get details of authenticated user (user_id, name), think they can be obtained from token, but I dont know how to do this. How is it possible to do ?
UPDATED
I am doing a post request
router.route('/articles')
.post(function (req, res) {
var article= new Article();
article.user_id = ???; // here needs user_id
article.imdb_id = req.body.imdb_id;
article.title = req.body.title;
article.thumb = req.body.thumb;
article.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Added' });
});
});
I need to insert into articles collection authors id (user_id), but I dont know how to get the authenticated user_id.
Tried to do this:
var token = req.body.token || req.query.token || req.headers['x-access-token'];
if (token) {
jwt.verify(token, app.get('superSecret'), function(err, decoded) {
if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' });
} else {
req.decoded = decoded;
console.log(decoded);
next();
}
});
decoded returns all info about user (name, password, _id). Is it possible to get only user_id and name from here?
When you sign a JSON web token you can pass it a user object. Here you can store whatever user data you need. This object is then signed and encoded and set as the token. When you send a request to your API passing the JWT in the auth header your validation function should return this user object back to you if the JWT is valid.
I like to use the Hapi framework for creating my Restful APIs so I will give an example using Hapi.
In your server.js file you need to register the hapi-auth-jwt2 package:
server.register(require('hapi-auth-jwt2'), (err) => {
if (err) {
throw err;
}
server.auth.strategy('jwt', 'jwt', {
key: config.jwt.secret,
validateFunc: auth.validate,
verifyOptions: { algorithms: ['HS256'] }
});
server.auth.default('jwt');
});
Your validation function:
export default {
validate: (tokenObject, req, callback) => {
validateToken(tokenObject.user_id, (err, user) => {
if (err) {
callback(Boom.unauthorized('User is unauthorized.'), false);
} else {
req.user = user;
callback(null, true);
}
});
}
};
The validateToken function should take the user id that you got from the token and query for the user. If a user is found then you know the token is valid and you can return and store the rest of the user information.
To create a token I use "jsonwebtoken" package:
generateToken: (user_id, name, callback) => {
'use strict';
callback(null, JWT.sign({
user_id: user_id,
name: name
}, config.JWT.SECRET, {
expiresIn: 86400
}));
}
Let's say you need to verify if the token sent from user In the headers already In your Database or not (we're going to call it protect)
const {promisify} = require('util');
const jwt = require('jsonwebtoken');
const User = require('./../models/userModel');
...
exports.protect = catchAsync(async(req, res, next) => {
// 1) Getting token and check if it's there in headers
let token;
//authorization is the name of the header token
if (req.headers.authorization) {
token = req.headers.authorization;
}
if (!token) {
return next(new AppError('You are not logged in! Please Login To get Access.', 401));
}
// 2) Verification Token is a valid token
const decoded = await promisify(jwt.verify)(token, process.env.JWT_SECRET);
// WE CAN GET THE USER ID FROM DECODED
// 3) Check if user still exists not deleted
const currentUser = await User.findById(decoded.id);
if (!currentUser) {
return next(new AppError('the user does not exist.', 401));
}else{
// WHAT EVER YOU WANT TO DO AFTER CHECKING USER FOUND IN DATABASE
})