Authentication with bcrypt and jwt does not work - node.js

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;

Related

Nodejs error when making a POST request [ERR_HTTP_HEADERS_SENT]

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.

JWT token is invaild

I use JWT for auth.
The auth will be a middleware:
const jwt = require('jsonwebtoken');
require('dotenv').config();
module.exports = (req, res, next) => {
const token = req.header("auth-token");
if (!token) return res.status(401).send("Missing token. Access denied");
try {
const decoded = jwt.verify(token, process.env.jwtKey);
req.user = decoded;
next();
} catch (err) {
console.log(err);
res.status(400).send('Invalid token.');
}
};
now when I get an token and use it in header "auth-token" and making a get or post request with the auth middleware its allways gives me "JsonWebTokenError: invalid signature"
I use the middleware like this:
router.get('/:id', auth, async (req, res) => {
const user = await User.findOne({
_id: req.params.id,
});
if (!user) return res.status(404).send('User not found');
res.send(user);
})
here is where the token is generated
router.post('/', async (req, res) => {
//check for validation errors
const { error } = validate(req.body);
if (error) return res.status(400).send(error.details[0].message);
let user = await User.findOne({ email: req.body.email });
if (!user) return res.status(404).send("Invalid email or password");
const validPassword = await bcrypt.compare(req.body.password, user.password);
if (!validPassword) return res.status(400).send("Invalid email or password");
res.json({ token: user.generateAuthToken(), status: "User is logged in" })
});
const validate = (req) => {
const schema = Joi.object({
email: Joi.string().min(6).max(255).email().required(),
password: Joi.string().min(6).max(255).required(),
});
return schema.validate(req);
};
module.exports = router;
You can see the auth middleware is used when I make the request GET
At JWT.io debugger when I put the token its says its ok... so whats worng?
So I sloved the issue, I had mistake with the key I decoded and key I veryfied.

Express routing and jsonwebtoken, staying logged in after token creation

I'm having a hard time connecting the last dots building a role based access control api in Express.
Following this tutorial and implementing onto my existing program, but I think I am missing the last step and after countless tutorials analysis paralysis has set in. I have since scaled back all my necessary code to what I think is the bare minimum.
Currently I am able to create a new user and save them to the mongoose database. I can see the hash by bcrypt is doing its thing and I can see the token being generated in the response after signing up. However as soon as I navigate to a new page after signup or login, for eg the users own id page/user/:userId as per tutorial, I keep getting You need to be logged in. I know I need to check for a token on every request but my question is, why doesn't it seem like the middleware is checking for the token or something is holding it back?
Since the token is shown in the json reponse surely I should be able to check for the tokens existence with the next get request at for eg the /user/:userId page? Isn't that the idea? Or is the browser just showing the response but I still need to actually store it? I don't understand where it goes to so to speak..
Any advice? Or is this a session thing? I know its a bit hard without all the code but if anyone could spot anything relevant so that I could research my next steps I would much appreciate it!
First this middleware in app.js
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.use('/', async (req, res, next) => {
if (req.headers['x-access-token']) {
try {
const accessToken = req.headers['x-access-token'];
const {userId, exp} = await jwt.verify(accessToken, process.env.JWT_SECRET);
console.log('token verified'); // not printing to console
// If token has expired
if (exp < Date.now().valueOf() / 1000) {
return res.status(401).json({
error: 'JWT token has expired, please login to obtain a new one',
});
}
res.locals.loggedInUser = await User.findById(userId);
next();
} catch (error) {
next(error);
}
} else {
next();
}
});
app.use('/', userRoutes);
I have built the roles using the module access-control which is required
const AccessControl = require('accesscontrol');
const ac = new AccessControl();
exports.roles = (function() {
ac.grant('basic')
.readOwn('profile')
.updateOwn('profile');
ac.grant('supervisor')
.extend('basic')
.readAny('profile');
ac.grant('admin')
.extend('basic')
.extend('supervisor')
.updateAny('profile')
.deleteAny('profile');
return ac;
})();
routes examples as per tutorial.
router.get('/signup', (req, res, next) => {
res.render('signup', {
viewTitle: 'User SignUp',
});
});
router.post('/signup', userController.signup);
router.get('/login', (req, res, next) => {
res.render('login', {
viewTitle: 'User Login - WTCT OPS',
});
});
router.post('/login', userController.login );
router.get('/add', userController.allowIfLoggedin, userController.grantAccess('readAny', 'profile'), userController.add);
router.get('/users', userController.allowIfLoggedin, userController.grantAccess('readAny', 'profile'), userController.getUsers);
router.get('/user/:userId', userController.allowIfLoggedin, userController.getUser);
router.put('/user/:userId', userController.allowIfLoggedin, userController.grantAccess('updateAny', 'profile'), userController.updateUser);
router.delete('/user/:userId', userController.allowIfLoggedin, userController.grantAccess('deleteAny', 'profile'), userController.deleteUser);
relevant part of controller
async function hashPassword(password) {
return await bcrypt.hash(password, 10);
}
async function validatePassword(plainPassword, hashedPassword) {
return await bcrypt.compare(plainPassword, hashedPassword);
}
// grant access depending on useraccess role
exports.grantAccess = function(action, resource) {
return async (req, res, next) => {
try {
const permission = roles.can(req.user.role)[action](resource);
if (!permission.granted) {
return res.status(401).json({
error: 'You don\'t have enough permission to perform this action',
});
}
next();
} catch (error) {
next(error);
}
};
};
// allow actions if logged in
exports.allowIfLoggedin = async (req, res, next) => {
try {
const user = res.locals.loggedInUser;
if (!user) {
return res.status(401).json({
error: 'You need to be logged in to access this route',
});
}
req.user = user;
next();
} catch (error) {
next(error);
}
};
// sign up
exports.signup = async (req, res, next) => {
try {
const {role, email, password} = req.body;
const hashedPassword = await hashPassword(password);
const newUser = new User({email, password: hashedPassword, role: role || 'basic'});
const accessToken = jwt.sign({userId: newUser._id}, process.env.JWT_SECRET, {
expiresIn: '1d',
});
newUser.accessToken = accessToken;
await newUser.save();
res.send({
data: newUser,
message: 'You have signed up successfully',
});
} catch (error) {
next(error);
}
};
exports.login = async (req, res, next) => {
try {
const {email, password} = req.body;
const user = await User.findOne({email});
if (!user) return next(new Error('Email does not exist'));
const validPassword = await validatePassword(password, user.password);
if (!validPassword) return next(new Error('Password is not correct'));
const accessToken = jwt.sign({userId: user._id}, process.env.JWT_SECRET, {
expiresIn: '1d',
});
await User.findByIdAndUpdate(user._id, {accessToken});
res.status(200).json({
data: {email: user.email, role: user.role},
accessToken,
});
} catch (error) {
next(error);
}
};
// get one user
exports.getUser = async (req, res, next) => {
try {
const userId = req.params.userId;
const user = await User.findById(userId);
if (!user) return next(new Error('User does not exist'));
// console.log(req.params);
res.send(200).json({
data: user,
});
} catch (error) {
next(error);
}
};
Why when trying to post to the endpoint /user/:userId is the middleware not checking for the token?
Thank you for any advice!
Update:
So far I have tried to removed the / from app.use. I saw I made that mistake now, but also tried removing it from the app.use(userRoutes); middleware to make it apply to all http requests but no luck.
app.use(async (req, res, next) => {
if (req.headers['x-access-token']) {
try {
const accessToken = req.headers['x-access-token'];
const {userId, exp} = await jwt.verify(accessToken, process.env.JWT_SECRET);
// If token has expired
if (exp < Date.now().valueOf() / 1000) {
return res.status(401).json({
error: 'JWT token has expired, please login to obtain a new one',
});
}
res.locals.loggedInUser = await User.findById(userId);
// console.log('Time:', Date.now());
next();
} catch (error) {
next(error);
}
} else {
next();
}
});
app.use(userRoutes);
I also thought that maybe because my server makes http requests in the backend maybe that was causing a problem in setting the x-access-token header? So I tried to change the x-access-token mw to use router.use on all routes but still nothing. I don't understand what I am missing. And just to be sure I'm not missing something fundamental, since I am using the JWT I do not need to use local storage or cookies to allow for browsing between pages while logged in since I can use the token set in the header, correct?
Thanks again for any advice!
That's because your middleware is only tied to the / route. Remove it if you want it to be used for every route. Take a look at the ExpressJS Docs regarding middleware.

Redirect to profile url after login

I am new in backend Node.js and till now I am able to complete registration and login with authentication.
When login I am getting token in response by using jwt token
Now I want to have the registration details to be shown to users after login. After login the details must of be of particular user's only whos is logging in.
And if admin is logging in, then he will get the entire database user's fields.
This is my index.route:-
const express = require ('express');
const router = express.Router();
const mongoose = require ('mongoose');
const User = mongoose.model('User');
const ctrlUser = require ('../controllers/user.controller.js');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const passport = require('passport');
// routing functions
//registartion user signup
router.post('/register' , ctrlUser.register);
//login user
router.post('/login' , (req, res, next) => {
User.find({email: req.body.email})
.exec()
.then(user => {
if(user.length < 1) {
return res.status(401).json({
message: "Auth failed. User not found."
})
}
bcrypt.compare(req.body.password, user[0].password, (err, result) =>{
if (err) {
return res.status(401).json({
message: "Auth failed. Check email and password"
});
}
if (result){
const adminEmail = "rohit#metapercept.com";
const role = user[0].email===adminEmail? "admin" : "user"; //check user id as admin or user
const token = jwt.sign(
{
email: user[0].email,
userId: user[0]._id,
role
},
process.env.JWT_KEY,
{
expiresIn : "1h"
});
return res.status(200).json({
message: "Auth Successful",
token : token
});
res.redirect('/profile');
}
});
})
.catch(err =>{
if (err.code == 500)
res.status(500).send(["Something went wrong in login"]);
else
return next(err);
});
});
router.get('/profile', function(req, res, next){
//something todo here ...
});
//delete user
router.delete('/:userId' , (req, res, next) =>{
User.deleteMany({_id: req.params.userId})
.exec()
.then(result => {
res.status(200).send(["Deleted"]);
})
.catch(err =>{
if (err.code == 500)
res.status(500).send(["Didn't get deleted"]);
else
return next(err);
});
});
module.exports = router;
How can I access user's details in profile url API?
Get JWT from request header then decode
jwt.verify(token, getKey, options, function(err, decoded) {
console.log(decoded.email)
});
jwt.verify - jwt doc
Create new middleware ( above other routes)
// route middleware to verify a token
router.use(function(req, res, next) {
// check header or url parameters or post parameters for token
var token = req.body.token || req.query.token || req.headers['x-access-token'];
// decode token
if (token) {
// verifies secret and checks exp
jwt.verify(token, app.get('superSecret'), function(err, decoded) { if (err) {
return res.json({ success: false, message: 'Failed to authenticate token.' }); } else {
// if everything is good, save to request for use in other routes
req.decoded = decoded; next();
}
});
} else {
// if there is no token
// return an error
return res.status(403).send({
success: false,
message: 'No token provided.'
});
}
});
Help : jwt - decode and save in req
In the code, After return your redirect never work. so There're 2 options:
You don't need to return a token to client, just use res.redirect('/profile') after your verification. (in this way, your server and client are in one)
You just return the token to client (Don't use res.redirect('/profile') anymore) then client will use that token to redirect to the profile. (in this way, your server and client are separate each other).

Unauthorized after login with passport-jwt

I used passport-jwt for login. it find the user and the user can login but when after logging in, I want to redirect to the user dashboard nothing happens and it shows Unauthorized the in browser when it redirect to localhost:8080/my/
can you find out what the problem is:
p.s: when I remove passport.authenticate('jwt', {session: false}) from my.js it works and it redirects to /my/. so I think the problem is with this part of code or JWTStrategy in auth.js. I don't know how to fix it!
here's my code : (sorry it's to much code)
auth.js:
//Create a passport middleware to handle User login
passport.use(new LocalStrategy({
usernameField: 'username',
passwordField: 'password'
}, async (username, password, done) => {
try {
//Find the user associated with the email provided by the user
await User.findOne({
username: username
}, async function (err, user) {
if (!user) {
//If the user isn't found in the database, return a message
return done(null, false, {
message: 'User not found'
});
}
//Validate password and make sure it matches with the corresponding hash stored in the database
//If the passwords match, it returns a value of true.
let validate = await user.isValidPassword(password);
if (!validate) {
return done(null, false, {
message: 'Wrong Password'
});
}
//Send the user information to the next middleware
return done(null, user, {
message: 'Logged in Successfully'
});
});
} catch (error) {
return done(error);
}
}));
passport.use(new JWTStrategy({
jwtFromRequest : ExtractJWT.fromAuthHeaderAsBearerToken(),
secretOrKey : 'secret_token'
}, function(jwt_payload, done) {
User.findOne({id: jwt_payload.sub}, function(err, user) {
if (err) {
return done(err, false);
}
if (user) {
return done(null, user);
} else {
return done(null, false);
// or you could create a new account
}
});
}));
authenticate.js :
router.post('/login', async (req, res, next) => {
passport.authenticate('local', async (err, user, info) => {
try {
if (err || !user) {
const error = new Error('An Error occured')
return next(error);
}
req.login(user, {
session: false
}, async (error) => {
if (error) return next(error)
//We don't want to store the sensitive information such as the
//user password in the token so we pick only the email and id
const body = {
_id: user._id,
username: user.username
};
//Sign the JWT token and populate the payload with the user email and id
const token = jwt.sign({
user: body
}, 'top_secret');
//Send back the token to the user
// return res.json({
// token
// });
res.redirect('/my/?token='+token);
});
} catch (error) {
return next(error);
}
})(req, res, next);
});
module.exports = router;
user.js
const mongoose = require('mongoose');
const Role = require('./role');
const bcrypt = require('bcrypt');
let Schema = mongoose.Schema;
let userSchema = new Schema({
.
.
.
});
userSchema.pre('save', async function(next){
//'this' refers to the current document about to be saved
const user = this;
//Hash the password with a salt round of 10, the higher the rounds the more secure, but the slower
//your application becomes.
const hash = await bcrypt.hash(this.password, 10);
//Replace the plain text password with the hash and then store it
this.password = hash;
//Indicates we're done and moves on to the next middleware
next();
});
userSchema.methods.isValidPassword = async function(password){
const user = this;
//Hashes the password sent by the user for login and checks if the hashed password stored in the
//database matches the one sent. Returns true if it does else false.
let compare= await bcrypt.compare(password , user.password);
return compare;
}
module.exports = mongoose.model('User' , userSchema);
my.js
//secure routes that only users with verified tokens can access.
//Lets say the route below is very sensitive and we want only authorized users to have access
//Displays information tailored according to the logged in user
router.get('/',passport.authenticate('jwt', {session: false}), (req, res) => {
//We'll just send back the user details and the token
res.json({
message : 'You made it to the secure route',
token : req.query.token
});
})
module.exports = router;
This problem usually occurs because of an invalid JWT. To authenticate a JWT with passport-jwt it has to be of the following format:
"JWT xxxx.xxxx.xxxx"
So maybe try changing the following line in your Authenticate.js file:
res.redirect('/my/?token='+token);
// CHANGE IT TO:
res.redirect('/my/?token=' + 'JWT ' + token);
I hope this solves your problem.

Resources