Skip to content

Passport

Introduction

Passport is middleware for Node.js that makes it easy to implement authentication and authorization. It is extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application. It is a comprehensive set of strategies support authentication using a username and password, Google, Facebook, Twitter, and more.

Username & Password

Lets create a small demo with username/password session based authentication in your Node.js application.

Passport library connects with express-session middleware and attach authenticated user information to req.session object. We will store this session object to MongoDB with connect-mongo package.

Now authentication will be detected with username/password Local Strategy in MongoDB. With LocalStrategy we are not going to create own function with steps to authenticate a user, instead we will use passport-local-mongoose simplifying the username and password authentication with our Mongoose User model.

Note

We are not use Node's crypto module to hash/verify password in this exercises. Read more about hashing/verifying password from Password web site Username/Password - example.

We are using passport-local-mongoose which will automatically do this for us.

Project and packages

First create a new password-demo project and install needed packages.

1
2
mkdir password-demo
cd password-demo
1
2
3
4
5
6
7
8
npm init -y
npm i express
npm i express-session
npm i mongoose
npm i connect-mongo
npm i passport
npm i passport-local
npm i passport-local-mongoose

online

Yes - you can install all of those in one line, but now those are listed line by line, so you can see those better!

Include needed packages

Include below packages and create an express application in your index.js.

We will use a following packages:

index.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const mongoose = require('mongoose');
const MongoStore = require('connect-mongo');
const app = express();
const port = 3000;

// Parse incoming requests with urlencoded payloads 
app.use(express.urlencoded({ extended: false }));

app.listen(port, () => {
  console.log(`Server is listening on port ${port}...`)
});

Create a User Model

Create a User Model to store User username, password and registrationDate. This model is leveraging the passport-local-mongoose package to simplify building username and password login with Passport.

Note

Passport-Local Mongoose will add hash and salt fields to stored user in MongoDB. You will see this later.

models/user.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const mongoose = require('mongoose');
const passportLocalMongoose = require('passport-local-mongoose');
const Schema = mongoose.Schema;

const UserSchema = new Schema({
  username: {
    type: String,
    required: true,
    unique: true
  },
  email: {
    type: String,
    required: true,
    unique: true
  },
  registrationDate: { 
    type: Date, 
    default: Date.now 
  }
});

UserSchema.plugin(passportLocalMongoose);

const UserModel = mongoose.model('User', UserSchema);
module.exports = UserModel;

Include User model in your index.js application:

index.js
1
2
// Include User model to communicate with MongoDB
const User = require("./models/user");

Connection to database

Connect your app to MongoDB and get a connection. We will fix some deprecation warnings.

index.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Connect your app to MongoDB and get connection
mongoose.set('strictQuery', false) 
mongoose.connect('YOUR_MONGODB_CONNECTION_STRING_HERE', 
  { 
    useNewUrlParser: true, 
    useUnifiedTopology: true
  }
);
const db = mongoose.connection
db.on('error', console.error.bind(console, 'MongoDB connection error:'));
db.once('open', () => console.log('Database successfully connected!'));

Session configuration to MongoDB

MongoDB Store will be used to save a user session. Session will be initialized with express-session middleware, with a secret session key and some default operations required when initializing a session in express.

  • store option will be used to save session to MongoStore
  • unset with destroy value will be used to remove stored session object from MongoDB when session is ended.
index.js
1
2
3
4
5
6
7
app.use(session({
  secret: process.env.SECRET_KEY,
  resave: false,
  saveUninitialized: true,
  unset: 'destroy', 
  store: MongoStore.create({ mongoUrl: 'YOUR_MONGODB_CONNECTION_STRING_HERE' })
}));

Local passport strategy

Create a local passport stategy and configure passport to persist user information in the login session with passport-local-mongoose. Now LocalStrategy strategy communicates with User model automatically and we don't need to create a steps with authentication. Check Password page how it can be done with step by step Verify Password.

Passport uses serializeUser function to persist user data after successful authentication into session. Function deserializeUser is used to retrieve user data from session. Finally you need to call initialize and session functions to initialize Passport use.

Understanding passport serialize deserialize

Good story about Understanding passport serialize deserialize

index.js
1
2
3
4
5
6
7
8
// Local passport strategy
passport.use(User.createStrategy());
// Configure Passport to persist user information in the login session
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
// Initialize Passport session
app.use(passport.initialize());
app.use(passport.session());

Register/Add a new user

A new user will be added with register function of passport-local-mongoose. This function will check that username is unique. Endpoint will send error or successfully message to the client.

index.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// Add/register a new User
app.post('/api/register', (req, res) => {
  const { email, username } = req.body
  User.register(
    new User({ 
      email: email, 
      username: username 
    }), req.body.password, (err, msg) => {
      if (err) {
        res.send(err);
      } else {
        res.send({ message: `User ${username} registered successfully!` });
      }
    }
  )
})

Use Postman to send a new register request to server. Click image to see it bigger!

!Image 01

Look your MongoDB and find out that a new document has been created to users collection.

!Image 08

Note

Notice that there is no field for password at all, instead passport-local-mongoose created salt and hash. We didn’t have to define salt and hash fields in User schema. Passport-local-mongoose will keep username unique, if username already exist it will give UserExistsError.

Try to add same user again. You will get UserExistsError with A user with the given username is already registered message. This will send to your with passport-local-mongoose package with User model automatically. Click image to see it bigger!

!Image 02

Login

Here we will use the local passport authenciation strategy with Passport - authenticate. In this route, passport.authenticate() is middleware which will authenticate the request. We will redirect request to login-ok or login-error routes, if there aren't any errors. Error situations will be handled with express error-handling.

index.js
1
2
3
4
5
6
7
// Login
app.post('/api/login', passport.authenticate('local', { 
  successRedirect: '/api/login-ok',
  failureRedirect: '/api/login-error' 
}), (err, req, res, next) => {
  if (err) next(err);
});

Now, in login-ok or login-error routes we only show req.session object in console and send response back to the client.

index.js
1
2
3
4
app.get('/api/login-ok', (req, res, next) => {
  console.log('login ok', req.session);
  res.send('Login successfully.');
});
index.js
1
2
3
4
app.get('/api/login-error', (req, res, next) => {
  console.log('login error', req.session);
  res.send('Login authorization error!');
});

Use Postman to send a login request to server. Click image to see it bigger!

!Image 03

Look stored session document from MongoDB.

!Image 09

Try to use username that doesn't exists or with wrong password. Click image to see it bigger!

!Image 04

Secret content endpoint

This secret endpoint is just created to test that login/authorization is working correctly. Here we call Passport's isAuthenticated function to check if the request is authenticated or not. It will return true in case an authenticated user is present in req.session.passport.user.

index.js
1
2
3
4
5
6
7
8
app.get('/api/secret', (req, res) => {
  console.log('secret', req.session)
  if (req.isAuthenticated()) {
    res.json({ message: 'This is secret/auth endpoint data!' })
  } else {
    res.json({ message: 'User is not authenticated!' })
  }
})

Use Postman to send a request to get some data with correct authentication user. Click image to see it bigger!

!Image 05

Logout

Final task is to logout and remove session object. Use Passport - logout to terminate a login session. Set req.session object to null and it will removed from MongoDB.

index.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
app.post('/api/logout', (req, res) => {
  console.log('logout', req.session) 
  req.logout( (err) => {
    if (err) { 
      res.send('Logout error!');
    } else {
      // destroy store object from MongoDB
      // - session object: unset: 'destroy'
      req.session = null;
      res.send('Logout successfully!');
    }
  });
});

Use Postman to send a logout request. Click image to see it bigger!

!Image 06

Try again call secret/authenticated data endpoint when user is logged out. You shoud get User is not authenticated! message as a response. Click image to see it bigger!

!Image 07

Read More

Goals of this topic

Understand

  • Authentication and authorization with Passport - username/password.