Disclaimer: This is a series about me, creating a web application in Node.js. The completed example is available here.
- Beginning Node.js - callbacks - part 1
- Beginning Node.js - REST API with Express 4 - part 2
- Beginning Node.js - REST API with a Mongodb backend - part 3
- Beginning Node.js - REST API with a Mongodb backend (refactoring) - part 4
- Beginning Node.js - Testing - part 5
- Beginning Node.js - local authentication with Passport-Local Mongoose- part 6
I have a deep respect for all those developers out there that write fantastic modules I can use in my projects. One little gem is passport-local-mongoose. I've written about it before but as you see I'm doing it again.
What is passport-local-mongoose?
You can plug-in passport-local-mongoose into your Mongoose Userschema. This decorates the User object with a set of methods. E.g.
- authenticate
- register
- setPassword
- serialize
- deserialize
It also hashes the passwords. This saves a lot of work.
What we should work on
- Users should be able to register
- Users should be able to authenticate
- Users should be able to change their password
- Users should be able to change their e-mail address
- There should be a 'forgot password' procedure
- Users should be able to delete their accounts
This article covers only 'register' and 'authenticate'. You can go ahead and clone the restaurant github repo for a full example.
To add local authentication to your app you'll need to run:
var mongoose = require('mongoose'), Schema = mongoose.Schema, passportLocalMongoose = require('passport-local-mongoose'); var User = new Schema({}); var options = ({missingPasswordError: "Foutief password"}); User.plugin(passportLocalMongoose,options); module.exports = mongoose.model('User', User)
Of course, you may add your own properties to the model:
var mongoose = require('mongoose'), Schema = mongoose.Schema, passportLocalMongoose = require('passport-local-mongoose'); var User = new Schema({ uuid: { type: String, required: false }, firstname: { type: String, required: true }, active: { type: String, required: false } }); var options = ({missingPasswordError: "Wrong password"}); User.plugin(passportLocalMongoose,options); module.exports = mongoose.model('User', User)
Now let's hook up Passport in our app.
Controller
Let's create a user controller which contains the register, the login and the getLogin functions (to check if a user has logged in).
Create a file named controller.user.js and put it in the app folder:
var mongoose = require('mongoose'); var User = require('./model.user'); exports.register = function (req, res) { console.log("registering: " + req.body.firstName); User.register(new User({ username: req.body.username, firstname: req.body.firstname }), req.body.password, function (err, user) { if (err) { console.log(err); return res.send(err); } else { res.send({ success: true, user: user }); } }); }; exports.login = function (req, res, next) { User.authenticate()(req.body.username, req.body.password, function (err, user, options) { if (err) return next(err); if (user === false) { res.send({ message: options.message, success: false }); } else { req.login(user, function (err) { res.send({ success: true, user: user }); }); } }); }; exports.getLogin = function (req, res) { console.log(req.user); if (req.user) { return res.send({ success: true, user: req.user }); } //res.send(500, {status:500, message: 'internal error', type:'internal'}); == deprecated res.send({ success: false, message: 'not authorized' }); };
What happens?
1. User.authenticate and User.register:
The User.authenticate and User.register are functions we get from passport-local-mongoose. I just took this code as an example.
2. Check if a user is logged in with 'if(req.user)'
If a user is logged in, the req.user property is populated with the user object.
So if it exists, the user is logged in.
Routes
//mongodb var mongoose = require('mongoose'); var User = require('./model.user'); var users = require('./controller.user'); var passport = require('passport'); var LocalStrategy = require('passport-local').Strategy; var cookieParser = require('cookie-parser'); var session = require('express-session'); module.exports = function (app) { //initialize passport passport.use(User.createStrategy()); // use static serialize and deserialize of model for passport session support passport.serializeUser(User.serializeUser()); passport.deserializeUser(User.deserializeUser()); //need this according to passport guide app.use(cookieParser()); app.use(session({ secret: 'the princess and the frog', saveUninitialized: true, resave: true })); app.use(passport.initialize()); app.use(passport.session()); //routes app.route('/register').post(users.register); app.route('/login').post(users.login); app.route('/login').get(users.getlogin); };
Add this file to main.js, like so:
require('./app/routes.user')(app);
This will be your completed main.js:
var express = require('express'); var app = express(); var bodyparser = require('body-parser'); var mongoose = require('mongoose'); app.use(bodyparser.urlencoded({ extended: true })); app.use(bodyparser.json()); mongoose.connect('mongodb://localhost:27017/restaurant'); require('./app/routes.food')(app); require('./app/routes.user')(app); app.use(express.static(__dirname + '/public')); app.set('port', process.env.PORT || 3001); app.listen(app.get('port')); console.log("the server is running on http://localhost:" + app.get('port'));
Let's try this
Install Postman or another REST API test tool.
- Don't forget to configure the headers: Content-Type application/json
First, let's register a user (click POST):
Second, let's login:
Check the login status:
The end of this series
This is where this series end. I hope anyone will enjoy this and at least learns something from my struggles. I know I did!
Your feedback is more than welcome by the way.
Hello Jaqueline and thank you for the useful article! I have the following question, I see in your github repo (https://github.com/jacqinthebox/Restaurant/blob/master/app/controller.user.js) how you use the setPassword method of passport-local-mongoose. My question is how to I create a password change form, where a user would submit in the same form his old password and his new one. Obviously for this to work I would have to check his old password to see that it matches to the one already in the database and if it matched I would update his password using the setPassword method, my question is what is the optimal way taking into account security issues, to implement this first step (password comparison).