Marko Serk
Marko Serk

Reputation: 47

ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client Passport

This question has been asked before but was unable to solve anything with the previous listings.

I'm trying to authenticate using passport-cas and I'm running into an error that I don't really know a way around.

I believe the issue lies in the bottom code:

user.save((err) => {
    if (err) { return done(err); }
    return done(null, user);
  });

particularly the done(null, user) which causes the error the first time a user tries to register.

Problem:

If the user clicks the login button twice then it logins in perfect. The reason for this is because it throws an ERR_HTTP_HEADERS_SENT error the first time, then redirects home. The second time, as said before works perfectly because the user is already in the database and does not need to be created.

Any help on this would be amazing, my code is below:

passport.js:

passport.use(new(require('passport-cas').Strategy)({
  ssoBaseURL: 'https://domain.edu/cas',
  serverBaseURL: 'http://localhost:8080'
}, function(netid, done) {
  User.findOne({
    netid: netid
  }, function(err, user) {
    if (err) {
      return done(err);
    }
    if (!user) {
      const user = new User({
        netid: netid
      });

      user.save((err) => {
        if (err) { return done(err); }
        return done(null, user);
      });
    }
    return done(null, user);
  });
}));

user.js:

exports.casLogin = function(req, res, next) {
  passport.authenticate('cas', function(err, user) {
    if (err) {
      return next(err);
    }
    if (!user) {
      return res.redirect('/');
    }
    req.logIn(user, function(err) {
      if (err) {
        return next(err);
      }
      return res.redirect('/');
    });
  })(req, res, next);
};

app.js:

const userController = require('./controllers/user');
app.get('/cas', userController.casLogin);

Upvotes: 1

Views: 723

Answers (1)

jfriend00
jfriend00

Reputation: 707308

You are calling your done() callback twice in this code:

passport.use(new(require('passport-cas').Strategy)({
  ssoBaseURL: 'https://domain.edu/cas',
  serverBaseURL: 'http://localhost:8080'
}, function(netid, done) {
  User.findOne({
    netid: netid
  }, function(err, user) {
    if (err) {
      return done(err);
    }
    if (!user) {
      const user = new User({
        netid: netid
      });

      user.save((err) => {
        if (err) { return done(err); }
        return done(null, user);             // calling it again here
      });
    }
    return done(null, user);                 // calling it first here
  });
}));

The issue is that the callback you pass to user.save() is called asynchronously and the return in there doesn't return from the parent function. So, execution of the parent function continues and the return done(null, user) is called when you don't want it to be.

You can fix it by adding another return.

passport.use(new(require('passport-cas').Strategy)({
  ssoBaseURL: 'https://domain.edu/cas',
  serverBaseURL: 'http://localhost:8080'
}, function(netid, done) {
  User.findOne({
    netid: netid
  }, function(err, user) {
    if (err) {
      return done(err);
    }
    if (!user) {
      const user = new User({
        netid: netid
      });

      user.save((err) => {
        if (err) { return done(err); }
        return done(null, user);
      });
      return;                            // <=== add this return
    }
    return done(null, user);
  });
}));

You could also make the if (!user) into an if/else instead of adding the above return.

Upvotes: 2

Related Questions