├── .gitignore ├── Makefile ├── README.md ├── app.js ├── bin └── www ├── models └── account.js ├── package.json ├── public └── stylesheets │ └── style.css ├── routes ├── index.js └── users.js ├── test └── test.user.js └── views ├── error.jade ├── index.jade ├── layout.jade ├── login.jade └── register.jade /.gitignore: -------------------------------------------------------------------------------- 1 | /log/*.log 2 | /tmp 3 | .DS_Store 4 | .idea/ 5 | .sass-cache 6 | *~ 7 | node_modules 8 | *.out 9 | npm-debug.log -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | @./node_modules/.bin/mocha 3 | 4 | .PHONY: test -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## User Authentication With Passport and Express 4 2 | 3 | View the blog post here: http://mherman.org/blog/2015/01/31/local-authentication-with-passport-and-express-4 -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | // dependencies 2 | var express = require('express'); 3 | var path = require('path'); 4 | var favicon = require('serve-favicon'); 5 | var logger = require('morgan'); 6 | var cookieParser = require('cookie-parser'); 7 | var bodyParser = require('body-parser'); 8 | var mongoose = require('mongoose'); 9 | var passport = require('passport'); 10 | var LocalStrategy = require('passport-local').Strategy; 11 | var flash = require('connect-flash'); 12 | 13 | var routes = require('./routes/index'); 14 | var users = require('./routes/users'); 15 | 16 | var app = express(); 17 | 18 | // view engine setup 19 | app.set('views', path.join(__dirname, 'views')); 20 | app.set('view engine', 'jade'); 21 | 22 | // uncomment after placing your favicon in /public 23 | //app.use(favicon(__dirname + '/public/favicon.ico')); 24 | app.use(logger('dev')); 25 | app.use(bodyParser.json()); 26 | app.use(bodyParser.urlencoded({ extended: false })); 27 | app.use(cookieParser()); 28 | app.use(require('express-session')({ 29 | secret: 'keyboard cat', 30 | resave: false, 31 | saveUninitialized: false 32 | })); 33 | app.use(passport.initialize()); 34 | app.use(flash()); 35 | app.use(passport.session()); 36 | app.use(express.static(path.join(__dirname, 'public'))); 37 | 38 | 39 | app.use('/', routes); 40 | 41 | // passport config 42 | var Account = require('./models/account'); 43 | passport.use(new LocalStrategy(Account.authenticate())); 44 | passport.serializeUser(Account.serializeUser()); 45 | passport.deserializeUser(Account.deserializeUser()); 46 | 47 | // mongoose 48 | mongoose.connect('mongodb://localhost/passport_local_mongoose_express4'); 49 | 50 | // catch 404 and forward to error handler 51 | app.use(function(req, res, next) { 52 | var err = new Error('Not Found'); 53 | err.status = 404; 54 | next(err); 55 | }); 56 | 57 | // error handlers 58 | 59 | // development error handler 60 | // will print stacktrace 61 | if (app.get('env') === 'development') { 62 | app.use(function(err, req, res, next) { 63 | res.status(err.status || 500); 64 | res.render('error', { 65 | message: err.message, 66 | error: err 67 | }); 68 | }); 69 | } 70 | 71 | // production error handler 72 | // no stacktraces leaked to user 73 | app.use(function(err, req, res, next) { 74 | res.status(err.status || 500); 75 | res.render('error', { 76 | message: err.message, 77 | error: {} 78 | }); 79 | }); 80 | 81 | 82 | module.exports = app; 83 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('passport-local-express4:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /models/account.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | const passportLocalMongoose = require('passport-local-mongoose'); 4 | 5 | const Account = new Schema({ 6 | username: String, 7 | password: String 8 | }); 9 | 10 | Account.plugin(passportLocalMongoose); 11 | 12 | module.exports = mongoose.model('accounts', Account); 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passport-local-express4", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www", 7 | "test": "make test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:mjhea0/passport-local-express4.git" 12 | }, 13 | "author": "Michael Herman ", 14 | "license": "MIT", 15 | "dependencies": { 16 | "body-parser": "^1.10.2", 17 | "chai": "~1.8.1", 18 | "connect-flash": "^0.1.1", 19 | "cookie-parser": "^1.3.3", 20 | "debug": "^2.1.1", 21 | "express": "^4.11.1", 22 | "express-session": "^1.10.1", 23 | "jade": "^1.9.1", 24 | "mocha": "~1.14.0", 25 | "mongoose": "^4.4.1", 26 | "morgan": "^1.5.1", 27 | "passport": "^0.2.1", 28 | "passport-local": "^1.0.0", 29 | "passport-local-mongoose": "^1.0.0", 30 | "serve-favicon": "^2.2.0", 31 | "should": "~2.1.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const passport = require('passport'); 3 | const Account = require('../models/account'); 4 | const router = express.Router(); 5 | 6 | 7 | router.get('/', (req, res) => { 8 | res.render('index', { user : req.user }); 9 | }); 10 | 11 | router.get('/register', (req, res) => { 12 | res.render('register', { }); 13 | }); 14 | 15 | router.post('/register', (req, res, next) => { 16 | Account.register(new Account({ username : req.body.username }), req.body.password, (err, account) => { 17 | if (err) { 18 | return res.render('register', { error : err.message }); 19 | } 20 | 21 | passport.authenticate('local')(req, res, () => { 22 | req.session.save((err) => { 23 | if (err) { 24 | return next(err); 25 | } 26 | res.redirect('/'); 27 | }); 28 | }); 29 | }); 30 | }); 31 | 32 | 33 | router.get('/login', (req, res) => { 34 | res.render('login', { user : req.user, error : req.flash('error')}); 35 | }); 36 | 37 | router.post('/login', passport.authenticate('local', { failureRedirect: '/login', failureFlash: true }), (req, res, next) => { 38 | req.session.save((err) => { 39 | if (err) { 40 | return next(err); 41 | } 42 | res.redirect('/'); 43 | }); 44 | }); 45 | 46 | router.get('/logout', (req, res, next) => { 47 | req.logout(); 48 | req.session.save((err) => { 49 | if (err) { 50 | return next(err); 51 | } 52 | res.redirect('/'); 53 | }); 54 | }); 55 | 56 | router.get('/ping', (req, res) => { 57 | res.status(200).send("pong!"); 58 | }); 59 | 60 | module.exports = router; 61 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | /* GET users listing. */ 5 | router.get('/', (req, res, next) => { 6 | res.send('respond with a resource'); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /test/test.user.js: -------------------------------------------------------------------------------- 1 | const should = require("should"); 2 | const mongoose = require('mongoose'); 3 | const Account = require("../models/account.js"); 4 | 5 | describe('Account', () => { 6 | 7 | before((done) => { 8 | const db = mongoose.connect('mongodb://localhost/test'); 9 | done(); 10 | }); 11 | 12 | after((done) => { 13 | mongoose.connection.close(); 14 | done(); 15 | }); 16 | 17 | beforeEach( (done) => { 18 | var account = new Account({ 19 | username: '12345', 20 | password: 'testy' 21 | }); 22 | 23 | account.save((error) => { 24 | if (error) console.log('error' + error.message); 25 | else console.log('no error'); 26 | done(); 27 | }); 28 | }); 29 | 30 | it('find a user by username', (done) => { 31 | Account.findOne({ username: '12345' }, (err, account) => { 32 | account.username.should.eql('12345'); 33 | console.log(" username: ", account.username); 34 | done(); 35 | }); 36 | }); 37 | 38 | afterEach((done) => { 39 | Account.remove({}, () => { 40 | done(); 41 | }); 42 | }); 43 | 44 | }); 45 | -------------------------------------------------------------------------------- /views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | .container 5 | if (!user) 6 | a(href="/login") Login 7 | br 8 | a(href="/register") Register 9 | if (user) 10 | p You are currently logged in as #{user.username} 11 | a(href="/logout") Logout 12 | -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | meta(name='viewport', content='width=device-width, initial-scale=1.0') 6 | link(href='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css', rel='stylesheet', media='screen') 7 | link(rel='stylesheet', href='/stylesheets/style.css') 8 | body 9 | if (error && error.length > 0) 10 | br 11 | h4.error-msg= error 12 | br 13 | 14 | block content 15 | 16 | script(src='http://code.jquery.com/jquery.js') 17 | script(src='http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js') -------------------------------------------------------------------------------- /views/login.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | .container 5 | h1 Login Page 6 | p.lead Say something worthwhile here. 7 | br 8 | form(role='form', action="/login",method="post", style='max-width: 300px;') 9 | .form-group 10 | input.form-control(type='text', name="username", placeholder='Enter Username') 11 | .form-group 12 | input.form-control(type='password', name="password", placeholder='Password') 13 | button.btn.btn-default(type='submit') Submit 14 |   15 | a(href='/') 16 | button.btn.btn-primary(type="button") Cancel -------------------------------------------------------------------------------- /views/register.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | .container 5 | h1 Register Page 6 | p.lead Say something worthwhile here. 7 | br 8 | form(role='form', action="/register",method="post", style='max-width: 300px;') 9 | .form-group 10 | input.form-control(type='text', name="username", placeholder='Enter Username') 11 | .form-group 12 | input.form-control(type='password', name="password", placeholder='Password') 13 | button.btn.btn-default(type='submit') Submit 14 |   15 | a(href='/') 16 | button.btn.btn-primary(type="button") Cancel --------------------------------------------------------------------------------