Bobby Shark
Bobby Shark

Reputation: 1074

NodeJS/express 4 : Can't set headers after they are sent while writing cookie

I'm setting up a module to grant authentification by checking cookies. This module is called on each routes and if there is no authentificated session it will compare the cookies with the database and grant session. After each successful compare, the module updates both database and cookie informations and that's where I get the error "Error: Can't set headers after they are sent."

So basicly I have my route "Index" :

var check_auth = require('./middleware/check_auth');
module.exports = function(app){

    app.get('/', check_auth, function(req, res){
        if(req.session.userid){
            res.render('index', { title: 'AUTH'});  
        }else{
            res.render('index', { title: 'NOT AUTH'});
        }
    });
};

and now my module check_auth :

var mysql = require('mysql');
var crypto = require('crypto');
var bcrypt = require('bcrypt');
var pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: '****',
    database: 'nodejs'  
});

function check_auth(req, res, next){
    if(!req.session.userid){
        var cookie_auth = req.signedCookies.auth;
        var cookie_db_id = req.signedCookies.db_id;
        if(cookie_auth && cookie_db_id){
            pool.getConnection(function(err, conn) {
                var db_id = false;
                query = conn.query('SELECT * FROM users WHERE id = ?', [cookie_db_id]);
                query.on('error', function(err){
                    throw err;
                });
                query.on('result', function(row){
                    db_id = row.id;
                    db_cookie = row.cookie;
                    db_username = row.username;
                });
                query.on('end', function(result){
                    if(db_id && cookie_auth == db_cookie){
                        console.log("OK");
                        req.session.username = db_username;
                        req.session.userid = db_id;
                        var salt = bcrypt.genSaltSync(10);
                        var crypt = crypto.randomBytes(64).toString();
                        var hash = bcrypt.hashSync(crypt, salt);                    
                        res.cookie('auth', hash, { expires: new Date(Date.now() + (1000 * 60 * 60 * 24 * 365)), signed: true, httpOnly: true, secure: true });                          
                        res.cookie('db_id', db_id, { expires: new Date(Date.now() + (1000 * 60 * 60 * 24 * 365)), signed: true, httpOnly: true, secure: true });
                        pool.getConnection(function(err, conn) {                        
                            conn.query('UPDATE users SET cookie = (?) WHERE id = ?', [hash, db_id], function(err, info){
                                if(err) throw err;
                            });
                        });
                        conn.release();                             
                    }
                });
            });
        }
    }
    next();
}

module.exports = check_auth;

The error appears at the line :

res.cookie('auth', hash, { expires: new Date(Date.now() + (1000 * 60 * 60 * 24 * 365)), signed: true, httpOnly: true, secure: true });                          
res.cookie('db_id', db_id, { expires: new Date(Date.now() + (1000 * 60 * 60 * 24 * 365)), signed: true, httpOnly: true, secure: true });

Is this error coming because I'm writing my cookie into an asynchronous function from mysql or is it something to do with the module/routes ? Anyway, any solution are welcome.

Upvotes: 1

Views: 1993

Answers (2)

didinko
didinko

Reputation: 470

Yes, you are correct about the asynchronous nature of the problem. A simple workaround would be to call the rendering functionality at the end of your query:

function check_auth(req, res, next){
if(!req.session.userid){
    var cookie_auth = req.signedCookies.auth;
    var cookie_db_id = req.signedCookies.db_id;
    if(cookie_auth && cookie_db_id){
        pool.getConnection(function(err, conn) {
            var db_id = false;
            query = conn.query('SELECT * FROM users WHERE id = ?', [cookie_db_id]);
            query.on('error', function(err){
                throw err;
            });
            query.on('result', function(row){
                db_id = row.id;
                db_cookie = row.cookie;
                db_username = row.username;
            });
            query.on('end', function(result){
                if(db_id && cookie_auth == db_cookie){
                    console.log("OK");
                    req.session.username = db_username;
                    req.session.userid = db_id;
                    var salt = bcrypt.genSaltSync(10);
                    var crypt = crypto.randomBytes(64).toString();
                    var hash = bcrypt.hashSync(crypt, salt);                    
                    res.cookie('auth', cookie_auth, { expires: new Date(Date.now() + (1000 * 60 * 60 * 24 * 365)), signed: true, httpOnly: true, secure: true });                           
                    res.cookie('db_id', db_id, { expires: new Date(Date.now() + (1000 * 60 * 60 * 24 * 365)), signed: true, httpOnly: true, secure: true });
                    pool.getConnection(function(err, conn) {                        
                        conn.query('UPDATE users SET cookie = (?) WHERE id = ?', [hash, db_id], function(err, info){
                            if(err) throw err;
                        });
                    });
                    conn.release();
                    if(req.session.userid){
                        res.render('index', { title: 'AUTH'});  
                    }else{
                        res.render('index', { title: 'NOT AUTH'});
                    }
                }
            });
        });
    }
}
next();}`

Upvotes: 0

Ben Fortune
Ben Fortune

Reputation: 32118

Due to the asynchronous nature, next will most likely be called immediately. Move your callback inside your end handler.

function check_auth(req, res, next) {
    if (!req.session.userid) {
        var cookie_auth = req.signedCookies.auth;
        var cookie_db_id = req.signedCookies.db_id;
        if (cookie_auth && cookie_db_id) {
            pool.getConnection(function(err, conn) {
                if(err) return next(err);
                var db_id = false;
                query = conn.query('SELECT * FROM users WHERE id = ?', [cookie_db_id]);
                query.on('error', function(err) {
                    return next(err);
                });
                query.on('result', function(row) {
                    db_id = row.id;
                    db_cookie = row.cookie;
                    db_username = row.username;
                });
                query.on('end', function(result) {
                    if (db_id && cookie_auth == db_cookie) {
                        console.log("OK");
                        req.session.username = db_username;
                        req.session.userid = db_id;
                        var salt = bcrypt.genSaltSync(10);
                        var crypt = crypto.randomBytes(64).toString();
                        var hash = bcrypt.hashSync(crypt, salt);
                        res.cookie('auth', cookie_auth, {
                            expires: new Date(Date.now() + (1000 * 60 * 60 * 24 * 365)),
                            signed: true,
                            httpOnly: true,
                            secure: true
                        });
                        res.cookie('db_id', db_id, {
                            expires: new Date(Date.now() + (1000 * 60 * 60 * 24 * 365)),
                            signed: true,
                            httpOnly: true,
                            secure: true
                        });
                        pool.getConnection(function(err, conn) {
                            conn.query('UPDATE users SET cookie = (?) WHERE id = ?', [hash, db_id], function(err, info) {
                                if (err) throw err;
                            });
                        });
                        conn.release();
                    }
                    return next();
                });
            });
        }
    } else {
        next();
    }
}

Upvotes: 3

Related Questions