loading...
Pagepro

What is PassportJS And How To Integrate It?

maniekm profile image Mariusz Updated on ・4 min read

By the official definition:

"Passport is authentication middleware for Node.js.

Extremely flexible and modular, Passport can be unobtrusively dropped in to any Express-based web application.

A comprehensive set of strategies support authentication using a username and password, Facebook, Twitter, and more."

So basically PassportJS manages session user for us and gives us a lot of strategies to use when we want to integrate login/register options in our application.

Let's see how can we integrate it.

Integrating PassportJS

Let’s start with a base for our app:

const express = require('express')

const app = express()
const port = 3000

app.listen(port, () => console.log(`App listening on port ${port}!`))

Now we can start configuring passport.

We will need to configure session middlewares as well as cookie-parser and body-parser:

const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser')
const expressSession = require('express-session')
const passport = require('passport')

...

app.use(cookieParser())          // 1
app.use(bodyParser.json())       // 2
app.use(bodyParser.urlencoded({
  extended: true,
}))                              // 3
app.use(expressSession({
  secret: '5om35ecr37',
  resave: false,
  saveUninitialized: false,
}))                              // 4
app.use(passport.initialize())   // 5
app.use(passport.session())      // 6

Let’s have a look and explain what each line does:

  1. Middleware which parses Cookie header values and adds cookies property to request object containing all the cookies keyed by cookie names.
  2. Parses request body as json data and adds it to body property in request object.
  3. Adds support for parsing request body in URL format.
  4. MIddleware for generating session cookies and matching them with data stored server side i.e logged-in user.
  5. Initializes passport core which runs strategies and manages them.
  6. Add support for managing user data in the session.

Adding local strategy

Now that we have all middlewares configured we can add a strategy to our app.

In this case, it will be a local strategy.

First, we need to install a selected strategy:

npm install passport-local

Or:

yarn add passport-local

Now we can configure it.

To simplify this tutorial I will return static user object in our callback:

const userStore = {
  findByUsername: () => ({
    id: 1,
    name: 'Test User',
    username: 'testuser',
    validatePassword: () => true
  })
}

passport.use(new LocalStrategy(
  (username, password, done) => {
    const user = userStore.findByUsername(username)

    // User does not exist in our database or password was invalid
    if (!user || !user.validatePassword(password)) {
      return done(null, false)
    }
    // If user was found and password is valid we can return user object
    // in callback
    return done(null, user)
  }
))

And that’s it!

Our login form logic is ready.

Session serializing and deserializing

When we are using passport with session support we have to implement two callbacks which passport will use to serialize and deserialize the user.

Serialize callback is called when the user is saved to the session.

We will use user id which will allow us to find the user in the database later.

passport.serializeUser((user, done) => {
  done(null, user.id)
})

Deserialize callback is used to find the user in the database based on data stored in cookies.

In our case data stored in cookies is user id.

const userStore = {
  ...,
  findUserById: userId => ({
    id: 1,
    name: 'Test User',
    username: 'testuser',
    validatePassword: () => true
  })
}

passport.deserializeUser((userId, done) => {
  // Find user in database by id from session
  const user = userStore.findUserById(userId)


  // If user was not found throw an error
  if (!user) {
    done('unathorized')
    return
  }


  // If user was found return it via callback, this user object will be  
  // available in  request object as user property
  done(null, user)
})

Adding login form

Now we can add login form to our app.

To do that let’s define simple HTML form.

<html>
<head>
  <title>Awesome login form</title>
</head>
<body>
  <form action="/" method="POST">
    <label for="username">
      Username:
    </label>
    <input name="username" id="username" />
    <label for="password">
      Password:
    </label>
    <input type="password" name="password" id="password" />
    <button type="submit">
      Login
    </button>
  </form>
</body>
</html>

And return this HTML file on root URL of our app:

const path = require('path')

app.get('/', (request, response) => {
  response.sendFile(path.resolve('./login.html'))
})

Now we have to define the path to handle POST requests from our form:

// This will print “Logged in!” text after successful login
app.post(
  '/',
  passport.authenticate('local'),
  (request, response) => {
    response.send('Logged in!')
  }
)

Let’s go back to our root URL handler and return different view if the user is logged in.

First, we have to create HTML file for our logged-in view:

<html>
<head>
  <title>Awesome logged in view</title>
</head>
<body>
  <a href='/logout'>Logout</a>
</body>
</html>

And change our root URL handlers like this:

// This will redirect user back to root url after logging in
app.post('/', passport.authenticate('local', { successRedirect: '/' }))

app.get('/', (request, response) => {
  const viewPath = request.isAuthenticated()
    ? './loggedIn.html'
    : './login.html'

  response.sendFile(path.resolve(viewPath))
})

As you can see I have defined “/logout” URL in our logged in view, so we have to handle that URL as well.

Clicking on this link will log out our user and redirect him back to the login form.

app.get('/logout', (request, response) => {
  request.logOut()

  response.redirect('/')
})

Summary

If you work with NodeJS, PasportJS is a great authentication tool, that you can easily customize for different strategies.

Let me know what you think!

Posted on Dec 12 '19 by:

maniekm profile

Mariusz

@maniekm

Building web & mobile apps with React & React Native.

Pagepro

We build web and mobile apps to accelerate web businesses.

Discussion

markdown guide