MEAN Stack : Authentication with Passport
Passport is authentication middleware for Node. It is designed to serve a singular purpose: authenticate requests.
In modern web applications, single sign-on using an OAuth provider such as Facebook or Twitter has become a popular authentication method. Services that expose an API often require token-based credentials to protect access.
Passport recognizes that each application has unique authentication requirements. Authentication mechanisms, known as strategies, are packaged as individual modules. Applications can choose which strategies to employ, without creating unnecessary dependencies.
app.post('/login', passport.authenticate('local', { successRedirect: '/', failureRedirect: '/login' }));
With express 4, the express boilerplate generator command line was extracted to it's own module express-generator because the generator app did not really share code with express web framework and express and generator app can be released independently.
To generate a basic project boilerplate, we start by installing the Express generator:
$ sudo npm install -g express-generator@4 /usr/local/bin/express -> /usr/local/lib/node_modules/express-generator/bin/express ...
The -g flag means we're doing global installation.
Let's create a project - passport-local:
$ express passport-local
Let's update package.json so that we can install dependencies when we do npm install:
{ "name": "passport-local", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www" }, "repository": { "type": "git", "url": "git@github.com:epic-math/NodeJS-MEAN.git" }, "author": "K Hong <k@epicmath.com>", "license": "MIT", "dependencies": { "body-parser": "^1.13.2", "chai": "~1.8.1", "cookie-parser": "^1.3.5", "express": "^4.13.1", "express-session": "^1.10.1", "jade": "^1.11.0", "mocha": "~1.14.0", "mongoose": "^3.8.22", "morgan": "^1.6.1", "passport": "^0.2.1", "passport-local": "^1.0.0", "passport-local-mongoose": "^1.0.0", "should": "~2.1.0", "serve-favicon": "^2.2.0", "debug": "^2.1.1" } }
With the package.json, let's install dependencies:
$ pwd /home/ubuntu/passport-local $ npm install
We can test our setup by running the app:
$ node /home/ubuntu/passport-local/bin/www
Or we can run it with:
$ node app.js
Or:
$ npm start
We may want to navigate to http://localhost:3000/ in the browser (in our case, http://54.67.103.65:3000/), and we should be able to see the "Welcome to Express" text staring back.
Let's install MongoDB:
$ sudo npm install -g mongodb $ sudo service mongod start
Here is the updated app.js:
// dependencies var express = require('express'); var path = require('path'); var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var mongoose = require('mongoose'); var passport = require('passport'); var LocalStrategy = require('passport-local').Strategy; var routes = require('./routes/index'); var users = require('./routes/users'); var app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); // uncomment after placing your favicon in /public //app.use(favicon(__dirname + '/public/favicon.ico')); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(require('express-session')({ secret: 'keyboard cat', resave: false, saveUninitialized: false })); app.use(passport.initialize()); app.use(passport.session()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', routes); // passport config var Account = require('./models/account'); passport.use(new LocalStrategy(Account.authenticate())); passport.serializeUser(Account.serializeUser()); passport.deserializeUser(Account.deserializeUser()); // mongoose mongoose.connect('mongodb://localhost/passport_local_mongoose_express4'); // 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;
Let's get the Mongoose up and running. Add a new file, models/account.js with the following code:
var mongoose = require('mongoose'); var Schema = mongoose.Schema; var passportLocalMongoose = require('passport-local-mongoose'); var Account = new Schema({ username: String, password: String }); Account.plugin(passportLocalMongoose); module.exports = mongoose.model('Account', Account);
We can test our setup again by running the app:
$ node /home/ubuntu/passport-local/bin/www
We may want to navigate to http://localhost:3000/ in the browser (in our case, http://54.67.103.65:3000/), and we should be able to see the "Welcome to Express" text staring back.
Now we want to modify routes/index.js:
var express = require('express'); var passport = require('passport'); var Account = require('../models/account'); var router = express.Router(); router.get('/', function (req, res) { res.render('index', { user : req.user }); }); router.get('/register', function(req, res) { res.render('register', { }); }); router.post('/register', function(req, res) { Account.register(new Account({ username : req.body.username }), req.body.password, function(err, account) { if (err) { return res.render("register", {info: "Sorry. That username already exists. Try again."}) } passport.authenticate('local')(req, res, function () { res.redirect('/'); }); }); }); router.get('/login', function(req, res) { res.render('login', { user : req.user }); }); router.post('/login', passport.authenticate('local'), function(req, res) { res.redirect('/'); }); router.get('/logout', function(req, res) { req.logout(); res.redirect('/'); }); router.get('/ping', function(req, res){ res.status(200).send("pong!"); }); module.exports = router;
To test, let's fire up the server:
$ node /home/ubuntu/passport-local/bin/www
We may want to navigate to http://localhost:3000/ping in the browser (in our case, http://54.67.103.65:3000/ping), and we should be able to see the "pong!" text staring back.
doctype html html head title= title meta(name='viewport', content='width=device-width, initial-scale=1.0') link(href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css', rel='stylesheet', media='screen') link(rel='stylesheet', href='/stylesheets/style.css') body block content script(src='http://code.jquery.com/jquery.js') script(src='//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js')
extends layout block content if (!user) a(href="/login") Login br a(href="/register") Register if (user) p You are currently logged in as #{user.username} a(href="/logout") Logout
extends layout block content .container h1 Login Page p.lead Say something worthwhile here. br form(role='form', action="/login",method="post", style='max-width: 300px;') .form-group input.form-control(type='text', name="username", placeholder='Enter Username') .form-group input.form-control(type='password', name="password", placeholder='Password') button.btn.btn-default(type='submit') Submit a(href='/') button.btn.btn-primary(type="button") Cancel
extends layout block content .container h1 Register Page p.lead Say something worthwhile here. br form(role='form', action="/register",method="post", style='max-width: 300px;') .form-group input.form-control(type='text', name="username", placeholder='Enter Username') .form-group input.form-control(type='password', name="password", placeholder='Password') button.btn.btn-default(type='submit') Submit a(href='/') button.btn.btn-primary(type="button") Cancel br h4 info
Here is the test for "register".
At "Submit", we get:
If we click "Logout":
If we use the registered user's credential to log back in:
After hitting "Submit, we get:
We get the following for an unauthorized user:
Let's check our MongoDB:
$ mongo MongoDB shell version: 3.0.6 connecting to: test Welcome to the MongoDB shell. ... > use passport_local_mongoose_express4 switched to db passport_local_mongoose_express4 > db.accounts.find() { "_id" : ObjectId("561...555"), "salt" : "7c24...304", "hash" : "a6a46371 ... 23c3", "username" : "epicmath", "__v" : 0 } >
We can see the following from the the check with the db:
- username is as we expected - "epicmath"
- _id pertains to the unique id associated with that document.
- __v is the version # for that specific documents.
- Instead of a password key, we have both a salt and a hash key.
Source available at https://github.com/epic-math/NodeJS-MEAN/passport-local.
I got this from User Authentication With Passport and Express 4. Basically, copy/paste level. The only difference might be it's done on remote. So, you may get more info from the original article.
Node.JS
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization