As mentioned earlier, we use Passport to deal with user authentication in our API. Here, we will see how to use and store sessions and encrypt a user password to maintain a secure authentication.
First of all, let's install and save the Passport middleware to the application:
npm install passport passport-local --save
app
express variable:// Passport configuration require('./server/config/passport')(passport);
passport.js
file and the necessary code inside the config
folder. We can name this file with any name. However, to demonstrate the use of the passport
module, we use the same name from the module. Create a passport.js
file in the config
folder and place the following code:// Import passport module var LocalStrategy = require('passport-local').Strategy; // Import the user model var User = require('../../server/models/user'), module.exports = function(passport) { // passport setup // serialize user passport.serializeUser(function(user, done) { done(null, user.id); }); // deserialize user passport.deserializeUser(function(id, done) { User.findById(id, function(err, user) { done(err, user); }); }); // Configure local login strategy passport.use('local-login', new LocalStrategy({ // change default username and password, to email andpassword usernameField : 'email', passwordField : 'password', passReqToCallback : true }, function(req, email, password, done) { if (email) { // format to lower-case email = email.toLowerCase(); } // asynchronous process.nextTick(function() { User.findOne({ 'local.email' : email }, function(err,user) { // if errors if (err) { return done(err); } // check errors and bring the messages if (!user) { // third parameter is a flash warning message return done(null, false, req.flash('loginMessage','No user found.')); } if (!user.validPassword(password)) { return done(null, false, req.flash('loginMessage','Warning! Wrong password.')); } else { // everything ok, get user return done(null, user); } }); }); })); // Configure signup local strategy passport.use('local-signup', new LocalStrategy({ // change default username and password, to email andpassword usernameField : 'email', passwordField : 'password', passReqToCallback : true }, function(req, email, password, done) { if (email) { // format to lower-case email = email.toLowerCase(); } // asynchronous process.nextTick(function() { // if the user is not already logged in: if (!req.user) { User.findOne({ 'local.email' : email },function(err, user) { // if errors if (err) { return done(err); } // check email if (user) { return done(null, false,req.flash('signupMessage','Warning! the email isalready taken.')); } else { // create the user var newUser = new User(); newUser.local.email = email; newUser.local.password =newUser.generateHash(password); newUser.save(function(err) { if (err) { throw err; } return done(null, newUser); }); } }); } else { // everything ok, register user return done(null, req.user); } }); })); };
We wrote a basic configuration for using Passport. You can find more information regarding this process at http://passportjs.org/guide/configure/ and http://passportjs.org/guide/username-password/.
Note that we implemented a warning message using the connect-flash
module. This is a simple module to show warning messages to a user. The flash messages are stored in the session.
Using this module, we can easily show messages just using the req.flash()
function, as we used in the previous code, by following these steps:
npm install connect-flash --save
server.js
file after the Mongoose variable with the following code:var flash = require('connect-flash'),
app.use()
function:....
app.use('/api/speakers', speakers);
// flash warning messages
app.use(flash());
app.use()
function in the server.js
file.server.js
file, after the flash
variable:var flash = require('connect-flash'),
var passport = require('passport'),
app.use()
function:// flash warning messages app.use(flash()); // Init passport authentication app.use(passport.initialize()); // persistent login sessions app.use(passport.session());
After performing these steps, we now have the baseline to use Passport, but we still need to perform some more steps. Let's go ahead and add session control and password encryption.
To take care of adding session control and password encryption, two important tasks, we will use two very useful middleware: express-session
, which as the name suggests controls the user session, and connect-mongo
to store the user session on MongoDB.
npm install express-session connect-mongo --save
server.js
file with the following highlighted code:var passport = require('passport'), // Modules to store session var session = require('express-session'), var MongoStore = require('connect-mongo')(session);
app.use()
function:app.use(passport.session()); // required for passport // secret for session app.use(session({ secret: 'sometextgohere', saveUninitialized: true, resave: true, //store session on MongoDB using express-session + connectmongo store: new MongoStore({ url: config.url, collection : 'sessions' }) }));
Note that, besides using the session, here we determined that MongoDB stores the user session in a collection called sessions.
You can find more information about express-session
at https://github.com/expressjs/session.
bcrypt-nodejs
module to deal with password encryption. The first step is to install the module. Open your terminal and type:npm install bcrypt-nodejs --save
Password encryption is a security measure widely adopted in web applications, and the Node.js ecosystem gives us this great alternative.
You can find more about bcrypt
for Node.js applications at https://github.com/shaneGirish/bcrypt-nodejs.
In the next section, we'll see how to use this middleware on a user model.
Let's continue to use the bcrypt
module and implement it in our user model.
First, let's create the file where we will use this module. Do not yet perform any change in the server.js
file as we did previously with other modules; this is due to the fact that we will use it only in the user model.
user.js
to the models
folder.user.js
file:// Import mongoose and bcrypt var mongoose = require('mongoose'), var bcrypt = require('bcrypt-nodejs'), var Schema = mongoose.Schema; // define the schema for our user model var userSchema = new Schema({ local: { email: String, password: String, } }); // generating a hash userSchema.methods.generateHash = function(password) { return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null); }; // validating if password is valid userSchema.methods.validPassword = function(password) { return bcrypt.compareSync(password, this.local.password); }; // create the model for users and export to app module.exports = mongoose.model('User', userSchema);
Note that we use bcrypt
to generate a hash for the password in the highlighted code and also a validate method (methods.validPassword
) to check whether the password is valid.
It's time to revise all changes made in the server.js
file, just to check whether something is missing. The highlighted lines of code indicate that they were added in this stage.
Your file should look like the following code:
// Import the Modules / assign modules to variables var express = require('express'), var path = require('path'), var favicon = require('static-favicon'), var logger = require('morgan'), var cookieParser = require('cookie-parser'), var bodyParser = require('body-parser'), var mongoose = require('mongoose'), var flash = require('connect-flash'), var passport = require('passport'), // Modules to store session var session = require('express-session'), var MongoStore = require('connect-mongo')(session); // Setup Routes var routes = require('./server/routes/index'), var users = require('./server/routes/users'), var speakers = require('./server/routes/speakers'), // Database configuration var config = require('./server/config/config.js'), // connect to our database mongoose.connect(config.url); // Check if MongoDB is running mongoose.connection.on('error', function() { console.error('MongoDB Connection Error. Make sure MongoDB isrunning.'), }); var app = express(); // Passport configuration require('./server/config/passport')(passport); // view engine setup app.set('views', path.join(__dirname, 'server/views')); app.set('view engine', 'ejs'), app.use(favicon()); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded()); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); // required for passport // secret for session app.use(session({ secret: 'sometextgohere', saveUninitialized: true, resave: true, //store session on MongoDB using express-session + connect mongostore: new MongoStore({ url: config.url, collection : 'sessions' }) })); // flash warning messages app.use(flash()); // Init passport authentication app.use(passport.initialize()); // persistent login sessions app.use(passport.session()); // using routes app.use('/', routes); app.use('/users', users); app.use('/api/speakers', speakers); // catch 404 and forward to error handler app.use(function(req, res, next) { var err = new Error('Not Found'), err.status = 404; next(err); }); // error handlers // development error handler // will print stacktrace if (app.get('env') == 'development') { app.use(function(err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: err }); }); } // production error handler // no stacktraces leaked to user app.use(function(err, req, res, next) { res.status(err.status || 500); res.render('error', { message: err.message, error: {} }); }); module.exports = app; app.set('port', process.env.PORT || 3000); var server = app.listen(app.get('port'), function() { console.log('Express server listening on port ' +server.address().port); });
18.224.44.53