ReactSensei

Passport JS Simple Tutorial

March 25, 2020

Passport JS

Install these dependencies

package.json

{
  "name": "nodejs-passport-login",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "devStart": "nodemon server.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bcryptjs": "^2.4.3",
    "express": "^4.17.1",
    "express-flash": "0.0.2",
    "express-handlebars": "^3.1.0",
    "express-session": "^1.17.0",
    "method-override": "^3.0.0",
    "passport": "^0.4.1",
    "passport-local": "^1.0.0"
  },
  "devDependencies": {
    "dotenv": "^8.2.0"
  }
}

Install everything

$ npm i

Keep all secret stuff safe

.env

SESSION_SECRET=rEIJ0aQjV1rhioWRdsH

.gitignore

node_modules
.env

server.js

// grab all environment variables
// are we in production?
if (process.env.NODE_ENV !== 'production') {
  // no, so grab environment variables
  require('dotenv').config();
}

// stuff we need for this app
const express = require('express');
const app = express();
const bcryptjs = require('bcryptjs');
const passport = require('passport');
const flash = require('express-flash');
const session = require('express-session');
const methodOverride = require('method-override');

// we are use passport for auth
const initializePassport = require('./passport-config');
initializePassport(
  passport,
  email => users.find(user => user.email === email),
  id => users.find(user => user.id === id)
);

// poor man's Database
const users = [];

// we are using handlebars
const exphbs = require('express-handlebars');
app.engine('handlebars', exphbs());
app.set('view-engine', 'handlebars');

// grab stuff off of body
app.use(express.urlencoded({ extended: false }));
// share info with end users
app.use(flash());
// making sure sessions are working
app.use(
  session({
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
  })
);
// start up passport
app.use(passport.initialize());
// persist our variables the entire session our user has
// this works with app.use(session(...)) above
app.use(passport.session());
// in home.handlebars
// <form action="/logout?_method=DELETE" method="POST">
app.use(methodOverride('_method'));

// routes
// home page
app.get('/', checkAuthenticated, (req, res) => {
  res.render('home.handlebars', { name: req.user.name });
});

// login page
app.get('/login', checkNotAuthenticated, (req, res) => {
  // show the login form
  res.render('login.handlebars');
});

// make login form send data
app.post(
  '/login',
  checkNotAuthenticated,
  passport.authenticate('local', {
    successRedirect: '/',
    failureRedirect: '/login',
    failureFlash: true,
  })
);

// register page
app.get('/register', checkNotAuthenticated, (req, res) => {
  // register form
  res.render('register.handlebars');
});

// make register form send data
app.post('/register', checkNotAuthenticated, async (req, res) => {
  try {
    const hashedPassword = await bcryptjs.hash(req.body.password, 10);
    users.push({
      id: Date.now().toString(),
      name: req.body.name,
      email: req.body.email,
      password: hashedPassword,
    });
    // now user can login so send them to login page
    // would be great if this auto-logged in but such is life
    res.redirect('/login');
  } catch {
    // problem houston, send back to register page
    res.redirect('/register');
  }
  // always good to test what users look like in terminal
  // console.log(users);
});

// logout
// we can't call this delete function from HTML
// We need to use a form and a POST method
// But delete is not supported by forms we can only use a POST
// To be able to delete we need to use a npm module called 'method-overide'
// (this will allow us to override our method that we're using so instead of using post we can actually call this delete method)
app.delete('/logout', (req, res) => {
  // passport gives us this method
  // it will clear our session and log our user out
  req.logOut();
  res.redirect('/login');
});

// check if the user is authenticated
function checkAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {
    // all good, proceed to next middleware!
    return next();
  }

  // if not, send them to login page
  res.redirect('/login');
}

// check if not authenticated
function checkNotAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {
    // send authententicated user to home page
    return res.redirect('/');
  }

  // if not, proceed to next middleware
  next();
}

// server is listening on port 3000
app.listen(3000);

passport-config.js

// just a simple username/password for passport
const LocalStrategy = require('passport-local').Strategy;
// bcryptjs works better than bcrypt
const bcryptjs = require('bcryptjs');

function initialize(passport, getUserByEmail, getUserById) {
  const authenticateUser = async (email, password, done) => {
    // get user from email
    const user = getUserByEmail(email);
    // check if user exists
    if (user === null) {
      // no user, alert end user
      return done(null, false, { message: 'No user with that email' });
    }

    try {
      // compare end user password with db password
      if (await bcryptjs.compare(password, user.password)) {
        return done(null, user);
      } else {
        // wrong password
        return done(null, false, { message: 'Password incorrect' });
      }
    } catch (e) {
      return done(e);
    }
  };
  // don't need to pass password as sent by default
  // pass email and password to function above
  passport.use(new LocalStrategy({ usernameField: 'email' }, authenticateUser));
  passport.serializeUser((user, done) => done(null, user.id));
  passport.deserializeUser((id, done) => {
    return done(null, getUserById(id));
  });
}

module.exports = initialize;

Views

views/layouts/main.handlebars

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Pasport JS</title>
</head>
<body>
 
   {{{body}}}

</body>
</html>

views/home.handlebars

<h1>Hi {{name}}</h1>

<form action="/logout?_method=DELETE" method="POST">
<button type="submit">Logout</button>
</form>

login.handlebars

<h1>Login</h1>
{{#if messages.error }}
{{messages.error}}
{{/if}}
<form action="/login" method="POST">
  <div>
    <label for="email">Email</label>
    <input type="email" id="email" name="email" required />
  </div>
  <div>
    <label for="password">Password</label>
    <input type="password" id="password" name="password" required />
  </div>
  <button type="submit">Login</button>
</form>

<a href="/register">Register</a>

register.handlebars

<h1>Register</h1>
<form action="/register" method="POST">
  <div>
    <label for="name">Name</label>
    <input type="text" id="name" name="name" required />
  </div>
  <div>
    <label for="email">Email</label>
    <input type="email" id="email" name="email" required />
  </div>
  <div>
    <label for="password">Password</label>
    <input type="password" id="password" name="password" required />
  </div>
  <button type="submit">Register</button>
</form>

<a href="/login">Login</a>

Github


© 2020