Getting security setup in Express with PassportJS had a slight learning curve to it for me. PassportJS has a variety of strategies that they use to implement security. I needed a custom security provider based on Azure Table Storage. Azure Table Storage is cheap and lightweight, so it was a great fit for a developer on a budget. With Passport the key object to implement is the User object. User implements a few key functions findOne, save, and isValidPassword. I also used the bcrypt package to encrypt the passwords. If you are looking for a more indepth tutorial on how to use PassportJS check out http://scotch.io/tutorials/javascript/easy-node-authentication-setup-and-local
var azure = require("azure-storage");
var q = require('q');
var config = require('../config.js');
var bCrypt = require('bcrypt-nodejs');
function User() { }
User.UserFactory = function() {
var user = {
userName : "",
password : "",
enabled : true,
email : "",
validated : false
};
user.isValidPassword = User.isValidPassword;
return user;
}
User.findOne = function (userName) {
var tableService = azure.createTableService(config.storageAccountKey);
var deferred = q.defer();
tableService.retrieveEntity('CloudUser', userName, 'true', function (err, result) {
if (err) {
if (err.statusCode === 404)
deferred.resolve(undefined);
else
deferred.reject(err);
}
else {
deferred.resolve(User.toUser(result));
}
});
return deferred.promise;
}
User.toUser = function (userEntity) {
var user = User.UserFactory();
user.userName = userEntity.PartitionKey._;
user.enabled = userEntity.RowKey._;
user.password = userEntity.password._;
user.email = userEntity.email._;
user.validated = userEntity.validated._;
return user;
};
User.toUserEntity = function (user) {
return {
PartitionKey: { '_': user.userName },
RowKey: { '_': 'true' },
password: { '_': user.password },
email: { '_': user.email },
validated: { '_': user.validated }
};
};
User.createUser = function (user) {
var deferred = q.defer();
var tableService = azure.createTableService(config.storageAccountKey);
function create(user) {
tableService.insertEntity('CloudUser', User.toUserEntity(user), function (err, result) {
if (err) {
deferred.reject({ status: false, reason: "error creating user", err: err });
}
else {
deferred.resolve({ status: true, reason: "" });
}
});
}
User.findOne(user.userName).then(function (result) {
if (result && result.length > 1) {
deferred.resolve({ status: false, reason: "username exists" });
}
else {
create(user);
}
}).
fail(function (err) {
deferred.reject({ status: false, reason: "error creating user";, err: err});
});
return deferred.promise;
}
User.save = function (user) {
var tableService = azure.createTableService(config.storageAccountKey);
tableService.updateEntity('CloudUser', user, function (err, result) {
if (err) {
deferred.reject({ status: false, reason: "error creating user" });
}
else {
deferred.resolve({ status: true, reason: "" });
}
});
}
User.isValidPassword = function (user, password) {
return bCrypt.compareSync(password, user.password);
}
// Generates hash using bCrypt
User.createHash = function (password) {
return bCrypt.hashSync(password);
}
module.exports = User;
Now that we a User object that can find, save and validate passwords – we need plug it into a local strategy.
module.exports = function (passport) {
passport.use('login', new LocalStrategy({
passReqToCallback : true
},
function (req, username, password, done) {
// check if a user with username exists or not
User.findOne(username).then(function(user){
if (!user) {
console.log('User Not Found with username ' + username);
return done(null, false, req.flash('message', 'User Not found.'));
}
// User exists but wrong password, log the error
if (!User.isValidPassword(user, password)) {
console.log('Invalid Password');
return done(null, false, req.flash('message', 'Invalid Password')); // redirect back to login page
}
// User and password both match, return user from done method
// which will be treated like success
return done(null, user);
}).fail(function (err) {
// In case of any error, return using the done method
return done(err);
});
})
);
}
Of course we will also need a strategy to sign up new users.
</pre>
module.exports = function (passport) {
passport.use('signup', new LocalStrategy({
passReqToCallback : true // allows us to pass back the entire request to the callback
},
function (req, userName, password, done) {
findOrCreateUser = function () {
// find a user in Mongo with provided username
User.findOne(userName).then(function (user) {
// already exists
if (user) {
console.log('User already exists with username: ' + username);
return done(null, false, req.flash('message', 'User Already Exists'));
} else {
// if there is no user with that email
// create the user
var newUser = User.UserFactory();
// set the user's local credentials
newUser.userName = userName;
newUser.password = User.createHash(password);
newUser.email = req.param('email');
newUser.firstName = req.param('firstName');
newUser.lastName = req.param('lastName');
// save the user
User.createUser(newUser).then(function (err) {
console.log('User Registration succesful');
return done(null, newUser);
}).fail(function (err) {
if (err) {
console.log('Error in Saving user: ' + err);
throw err;
}
});
}
}).fail(function (err) {
// In case of any error, return using the done method
if (err) {
console.log('Error in SignUp: ' + err);
return done(err);
}
});
};
// Delay the execution of findOrCreateUser and execute the method
// in the next tick of the event loop
process.nextTick(findOrCreateUser);
})
);
}
And finally plug the strategies into Passport with the following code.
// Initialize Passport
app.use(passport.initialize());
app.use(passport.session());
// Passport needs to be able to serialize and deserialize users to support persistent login sessions
passport.serializeUser(function (user, done) {
done(null, user.userName);
});
passport.deserializeUser(function (id, done) {
User.findOne(id).then(function (user) {
done(null, user);
}).fail(function (err) {
done(err);
});
});
// Setting up Passport Strategies for Login and SignUp/Registration
login(passport);
signup(passport);
You can checkout the code at https://github.com/rocketcoder/passportjs-AzureTableStorge