I spent a whole evening figuring out authentication. I also started refactoring my code.
- I've renamed /server/app.js to server.js to distuinguish from the app.js file in the /public/javascripts folder.
- I've renamed the folder 'javascripts' to 'js'
- I've refactored the api.js and now use 'module.exports'
What does the application need?
I decided to start simple and implement local authentication against MongoDb.
Authentication in Nodejs is handled by the middleware (Express, but it originates from Connect).
There is a fantastic module that handles authentication for you, it's called Passport.js. Authenticating requests is as simple as calling passport.authenticate() and specifying which strategy to employ (in our case it will be LocalStrategy).
Passport-local-mongoose
Then there is passport-local-mongoose. It adds a Mongoose flavour over Passport, like class inheritance.
It adds extra properties (username and password) to your schema, although you can change 'username' to 'email'. It also adds a hash and salt field to store the username, the hashed password and the salt value.
Learn to Read the Source, Luke
I studied the login example back and forth and I actually think I'm grasping it now. The source code never lies. It *is* better than documentation.
Install it like so:
npm-install passport-local passport passport passport-local
Next, rewrite the schema:
var mongoose = require('mongoose'); var Schema = mongoose.Schema; var passportLocalMongoose = require('passport-local-mongoose'); var memberSchema = new Schema({ nickname : String, email: String, firstName: String, lastName: String, // password: String, education: [{ title: String, institute: String, certification: Boolean}] }); memberSchema.plugin(passportLocalMongoose); module.exports = mongoose.model('Member', memberSchema);
As you see I commented out the password attribute, as it is automatically added through passport-local-mongoose.
This is the new server.js:
var express = require('express'); var http = require('http'); var path = require('path'); var mongoose = require('mongoose'); //!!! added for passport var passport = require('passport'); var LocalStrategy = require('passport-local').Strategy; var Member = require('./models/Member'); var app = express(); // all environments app.set('port', process.env.PORT || 3000); app.use(express.favicon()); app.use(express.json()); app.use(express.urlencoded()); app.use(express.logger()); app.use(express.methodOverride()); app.use(express.cookieParser()); //!!! added for passport app.use(express.session({ secret: 'keyboard cat' })); app.use(passport.initialize()); app.use(passport.session()); app.use(app.router); app.use(express.static(path.join(__dirname, '../public'))); // development only if ('development' == app.get('env')) { app.use(express.errorHandler()); } //!!! added for passport passport.use(new LocalStrategy(Member.authenticate())); passport.serializeUser(Member.serializeUser()); passport.deserializeUser(Member.deserializeUser()); mongoose.connect('mongodb://localhost:27017/local'); //!!! moved the routes to separate file var api = require('./routes/api')(app); app.all('/', function(req, res) { res.sendfile('index.html', { root: "../public" }); }); http.createServer(app).listen(app.get('port'), function(){ console.log('Express server listening on port ' + app.get('port')); });
I moved the routes to a separate file. The new routes.api looks like this:
var passport = require('passport'); var mongoose = require('mongoose'); var Member = require('../models/Member'); module.exports = function (app) { //login section app.post('/login', passport.authenticate('local'), function(req, res) { if(req.user) { res.send(req.user._id); } }); app.post('/register', function (req, res) { Member.register(new Member({ username : req.body.username }), req.body.password, function(err, member) { if (err) { return res.send(err); } res.send(member); }); }); //member section // /members GET app.get('/members',passport.authenticate('local'), function(req,res) { Member.find({}, function(err, data) { res.send(data); }); }); // /member/id GET app.get('/member/:id',passport.authenticate('local'), function(req, res) { Member.findOne({ _id : req.params.id }, function(err, data) { res.send(data); }); }); app.put('/member/:id', passport.authenticate('local'), function(req, res) { delete req.body._id; Member.update({_id: req.params.id}, req.body, function(err, affected) { res.send(err); }); }); app.post('/member',passport.authenticate('local'),function(req,res) { Member.create(req.body, function(err, member) { res.send(member); }); }); }
The biggest gotcha
The login action returns the Mongo ID of the user. Passports saves the user details in req.user.
The id is then returned like so: res.send(req.user._id);
Register a user
After registering successfully, it is time to log on:
Log on
In return you get the ID of the logged in user.
Very informative. Thank you 😀
Really appreciated your series of Mean blogs Ladies. I just quoted you in a comment to a podcast be Jesse Liberty on javascript and the .Net world at http://jesseliberty.com/2014/01/27/yet-another-podcast-119-asp-net-angular/ .
Trust you don’t mind.
John Christchurch Dorset UK