0

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

jps
  • 20,041
  • 15
  • 75
  • 79
Patrick
  • 21
  • 1
  • 7
  • why do need `res.render('index')`?, If it is success response, you need to move inside `register function` – Naren Dec 05 '20 at 12:24
  • I'm not sure `res.status(400).json({message: 'Passwords do not match!'}).render('index')` calling `render` after `json()`. I never seen that, Can we do like that?. And There's no problem with `bcrypt ` – Naren Dec 05 '20 at 12:33

2 Answers2

0

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
        };
    });
};

Naren
  • 4,152
  • 3
  • 17
  • 28
0

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;
Patrick
  • 21
  • 1
  • 7