4

The code

app.js:

var express = require('express');
var session = require('express-session');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var mongoStore = require('connect-mongo')(session);
var mongoose = require('mongoose');
var passport = require('passport');

var config = require('./config');
var routes = require('./routes');

var mongodb = mongoose.connect(config.mongodb);

var app = express();

// view engine setup
app.set('views', config.root + '/views');
app.set('view engine', 'jade');
app.engine('html', require('ejs').renderFile);

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: false
}));
app.use(cookieParser());
app.use(express.static(config.root + '/public'));

app.use(session({
  name: 'myCookie',
  secret: 'tehSecret',
  resave: true,
  saveUninitialized: true,
  unset: 'destroy',
  store: new mongoStore({
    db: mongodb.connection.db,
    collection: 'sessions'
  })
}));

app.use(passport.initialize());
app.use(passport.session());

app.use('/', routes);

app.set('port', config.port);

var server = app.listen(app.get('port'), function() {
  if (config.debug) {
    debug('Express server listening on port ' + server.address().port);
  }
});

routes.js:

var express = require('express');
var router = express.Router();

var config = require('../config');
var userController = require('../controllers/user');
var authController = require('../controllers/auth');

router.get('/', function(req, res) {
  res.render('index', {
    title: config.app.name
  });
});

router.route('/users')
  .post(userController.postUsers)
  .get(authController.isAuthenticated, userController.getUsers);
router.get('/signout', userController.signout);

module.exports = router;

models/user.js:

var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');

var UserSchema = new mongoose.Schema({
  username: {
    type: String,
    unique: true,
    required: true
  },
  password: {
    type: String,
    required: true
  }
});

// Execute before each user.save() call
UserSchema.pre('save', function(callback) {
  var user = this;

  // Break out if the password hasn't changed
  if (!user.isModified('password')) return callback();

  // Password changed so we need to hash it
  bcrypt.genSalt(5, function(err, salt) {
    if (err) return callback(err);

    bcrypt.hash(user.password, salt, null, function(err, hash) {
      if (err) return callback(err);
      user.password = hash;
      callback();
    });
  });
});

UserSchema.methods.verifyPassword = function(password, cb) {
  bcrypt.compare(password, this.password, function(err, isMatch) {
    if (err) return cb(err);
    cb(null, isMatch);
  });
};

// Export the Mongoose model
module.exports = mongoose.model('User', UserSchema);

controllers/user.js:

var config = require('../config');

var User = require('../models/user');

exports.postUsers = function(req, res) {
  if (config.debug)
    console.log("user.postUsers()");

  var user = new User({
    username: req.body.username,
    password: req.body.password
  });

  user.save(function(err) {
    if (err)
      return res.send(err);

    if (config.debug)
      console.log("saved");

    res.json({
      message: 'New user created!'
    });
  });
};

exports.getUsers = function(req, res) {
  if (config.debug)
    console.log("user.getUsers()");

  User.find(function(err, users) {
    if (err)
      return res.send(err);

    if (config.debug)
      console.log("users", users);

    res.json(users);
  });
};

exports.signout = function(req, res) {
  if (config.debug)
    console.log("user.signout()");

  res.clearCookie('myCookie');

  req.session.destroy(function(err) {
    req.logout();
    res.redirect('/');
  });
};

controllers/auth.js:

var passport = require('passport');
var BasicStrategy = require('passport-http').BasicStrategy;

var config = require('../config');
var User = require('../models/user');

passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

passport.use(new BasicStrategy(
  function(username, password, done) {
    User.findOne({
      username: username
    }, function(err, user) {
      if (err) {
        return done(err);
      }

      // No user found with that username
      if (!user) {
        return done(null, false);
      }

      // Make sure the password is correct
      user.verifyPassword(password, function(err, isMatch) {
        if (err) {
          return done(err);
        }

        // Password did not match
        if (!isMatch) {
          return done(null, false);
        }

        // Success
        return done(null, user);
      });
    });
  }
));

exports.isAuthenticated = passport.authenticate('basic', {
  session: false
});

The problem

/signout route does not end the current session. In the req.session.destroy callback the req.session is undefined, yet a new GET request to /users acts like the session is valid.

Can someone help clear this problem out?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Saran
  • 3,845
  • 3
  • 37
  • 59
  • Can you get to /users without logging in? – Jason Nichols Oct 26 '14 at 21:57
  • @JasonNichols nope. Basic Auth is fired. But, once I log in, I can't log out. – Saran Oct 26 '14 at 21:59
  • Could you please console.log(req.user) in your route?And btw. afaik in your serialize user it should actually be user._id, brcause mongodb uses _id and not id by default. – cschaeffler Oct 27 '14 at 00:07
  • I think you can't logout basic auth easily, see http://stackoverflow.com/questions/233507/how-to-log-out-user-from-web-site-using-basic-authentication – vesse Oct 27 '14 at 03:36
  • @cschaeffler the `req.user` was undefined. I've added `authController.isAuthenticated` also to the /signout route and now the `req.user` is the currently logged in user object. Nevertheless, logout is not performed. – Saran Oct 27 '14 at 09:22
  • did you try after setting up Authenticated as middleware to use req.logout() without the req.session.destroy()? like just outside of that function.. – cschaeffler Oct 27 '14 at 15:03
  • @cschaeffler, yes I did. See http://stackoverflow.com/questions/26573227/express-session-wont-log-out?noredirect=1#comment41781960_26582572 – Saran Oct 27 '14 at 16:02
  • Now as I get this, you are actually using a BasicAuth strategy, which uses Basic HTTP Auth to authenticate your users. Basic HTTP Auth doesn't support logging someone out. So you should actually switch to a LocalStrategy from passport instead. However, if you really need to do Basic HTTP Auth, you should respond with res.status(401) and redirect him to where ever you want. – cschaeffler Oct 27 '14 at 16:16
  • @cschaeffler Yea. I also found somewhere a comment that BasicAuth does not support logout, but didn't find anything in the passport docs. I already came to the same solution for logout as you suggested (see http://stackoverflow.com/a/26585325/451962). Thanks though :) – Saran Oct 27 '14 at 16:21

4 Answers4

16

If, like me, you came here as a result of question title rather than full details- the answer is req.session.destroy(). I think the logout function is particular to passport.js and will not work if you are using standard express-session.

Andrew
  • 547
  • 4
  • 13
5

Solution

controllers/user.js:

exports.signout = function(req, res) {
  if (config.debug)
    console.log("user.signout()");

  req.logout();
  res.send(401);
};

Btw. don't mind the session(s) still being in DB immediately after the logout. Mongod checks and clears those out after 60 s.

Saran
  • 3,845
  • 3
  • 37
  • 59
0

in sign out api without using req.session.destroy() try req.logout();. I hope it will work.

Kundu
  • 3,944
  • 3
  • 16
  • 21
  • 1
    Sorry, that hasn't worked either. (That was my first approach before I tried `destroy`). – Saran Oct 27 '14 at 07:43
0

In my case the server-side code was fine. It was the client-side code where I wasn't including the withCredentials parameter when making the http request.

Below is the correct working code.

// server side (nodejs)
authRouter.post("/logout",
    passport.session(),
    checkAuthenticationHandler,
    async (req, res, next) => {
        req.logOut(err => {
            if (err) next(err)
            res.status(http.statusCodes.NO_CONTENT).end()
        })
    })
// client side (reactjs)
export const logout = async () => {
    const _response = await axios({
        method: 'post',
        url: `${authApi}/auth/logout`,
        withCredentials: true
    })
}

Gilbert
  • 2,699
  • 28
  • 29