Introduction

We will build a todo list app in this tutorial, complete with functionality that allows users to sign in with a username and password. In this tutorial, you will learn how to use Passport for authentication.

If you want to see where we are headed, here's an example of the final result: https://github.com/passport/todos-express-password

Before we dive in, you'll need a working development environment with Node.js and Git, as well as an editor and terminal of your choosing. Take a moment to set up these tools if you have not already done so.

Let's get started!

We are going to start with a starter app, which has all the scaffolding needed to build a todo list. Let's clone the app:

$ git clone https://github.com/passport/todos-express-starter.git username-password-tutorial

You now have a directory named 'username-password-tutorial'. Let's cd into it:

$ cd username-password-tutorial

Take a moment browse through the files in the starter app. As we work through this tutorial, we'll be using Express as our web framework, along with EJS as our template engine and CSS for styling. We will use SQLite as our database for storing data. Don't worry if you are not familiar with these technologies -- the necessary code will be provided at each step.

Now, let's install the dependencies:

$ npm install

And start the server:

$ npm start

Let's check to see if its working. Open http://localhost:3000 in your browser. You should be greeted with a page explaining how todos help you get things done.

Next, we will add a login page to the app.

Login Prompt

We want to let users sign in with a username and password. For that, we need a login page that prompts the user for their username and password. Let's add that now.

Let's create a file that will contain authentication-related routes:

$ touch routes/auth.js

Add the following code to that file, which creates a login route that will render the login page.

var express = require('express');

var router = express.Router();

router.get('/login', function(req, res, next) {
  res.render('login');
});

module.exports = router;

Next, we need to add this route to the app. Open 'app.js' and require the newly created auth routes at line 10, below where 'routes/index' is require'd:

var indexRouter = require('./routes/index');
var authRouter = require('./routes/auth');

Continuing within 'app.js', use the newly require'd authRouter at line 27, below where indexRouter is use'd.

app.use('/', indexRouter);
app.use('/', authRouter);

The login page has been added to our app! Let's see how it looks.

Start the server:

$ npm start

And open http://localhost:3000 and click "Sign in." We are prompted to sign in, but there's no place to enter a username and password.

For that we need an HTML form. Let's add that now.

Open 'views/login.ejs' and add the form at line 15, below the "Sign in" heading:

<h1>Sign in</h1>
<form action="/login/password" method="post">
    <section>
        <label for="username">Username</label>
        <input id="username" name="username" type="text" autocomplete="username" required autofocus>
    </section>
    <section>
        <label for="current-password">Password</label>
        <input id="current-password" name="password" type="password" autocomplete="current-password" required>
    </section>
    <button type="submit">Sign in</button>
</form>

Refresh the page. We've now got a login page that prompts the user for their username and password. Next, we will verify the username and password.

Verify Password

Now that we are prompting the user for their username and password, the next step is to verify the password.

To do that, we are going to use Passport and the passport-local strategy. Install both as dependencies:

$ npm install passport
$ npm install passport-local

Next, we need to configure Passport. Open 'routes/auth.js' and require the newly installed packages at line 2, below where express is require'd:

var express = require('express');
var passport = require('passport');
var LocalStrategy = require('passport-local');
var crypto = require('crypto');
var db = require('../db');

The built-in crypto module and the app's database are also require'd.

Add the following code at line 7 to configure the LocalStrategy.

passport.use(new LocalStrategy(function verify(username, password, cb) {
  db.get('SELECT * FROM users WHERE username = ?', [ username ], function(err, row) {
    if (err) { return cb(err); }
    if (!row) { return cb(null, false, { message: 'Incorrect username or password.' }); }

    crypto.pbkdf2(password, row.salt, 310000, 32, 'sha256', function(err, hashedPassword) {
      if (err) { return cb(err); }
      if (!crypto.timingSafeEqual(row.hashed_password, hashedPassword)) {
        return cb(null, false, { message: 'Incorrect username or password.' });
      }
      return cb(null, row);
    });
  });
}));

This configures the LocalStrategy to fetch the user record from the app's database and verify the hashed password that is stored with the record. If that succeeds, the password is valid and the user is authenticated.

Next, we need to add a route that will process the form submission when the user clicks "Sign in."

Continuing within 'routes/auth.js', add this route at line 28, below the '/login' route:

router.post('/login/password', passport.authenticate('local', {
  successRedirect: '/',
  failureRedirect: '/login'
}));

We've now got a route that will process the form on the login page! Let's test it out to see what happens.

Start the server:

$ npm start

Open http://localhost:3000, click "Sign in," and enter the following credentials:

Username: alice
Password: letmein

Click "Sign in."

Uh oh... we are informed that there's an error related to sessions. Next, we will fix that by establishing a session.

Establish Session

Once we've verified the user's password, we need to a login session to remember the fact that the user has authenticated as they navigate the app.

To do that, we'll add session support. Begin by installing the necessary dependencies:

$ npm install express-session
$ npm install connect-sqlite3

Open 'app.js' and require the additional dependencies at line 8, below where 'morgan' is require'd:

var logger = require('morgan');
var passport = require('passport');
var session = require('express-session');

var SQLiteStore = require('connect-sqlite3')(session);

Add the following code at line 29, after express.static middleware, to maintain and authenticate the session.

app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: false,
  store: new SQLiteStore({ db: 'sessions.db', dir: './var/db' })
}));
app.use(passport.authenticate('session'));

Finally, we need to configure Passport to manage the login session. Open 'routes/auth.js' and add the following code at line 22:

passport.serializeUser(function(user, cb) {
  process.nextTick(function() {
    cb(null, { id: user.id, username: user.username });
  });
});

passport.deserializeUser(function(user, cb) {
  process.nextTick(function() {
    return cb(null, user);
  });
});

Now, let's retry signing in. Open http://localhost:3000, click "Sign in," and enter the following credentials:

Username: alice
Password: letmein

Click "Sign in."

We are logged in! Go ahead and enter some tasks you've been needing to get done.

At this point, users can sign in with a username and password! Next, we will add the ability to sign out.

Log Out

Now that users can sign in, they'll need a way to sign out.

Open 'routes/auth.js' and add this route at line 45, below the '/login/password' route:

router.post('/logout', function(req, res, next) {
  req.logout(function(err) {
    if (err) { return next(err); }
    res.redirect('/');
  });
});

Return to the app, where you should already be signed in, and click "Sign out."

We've now got a working app where users can sign in and sign out! Next we will let new users sign up.

Sign Up

Users can now sign in and sign out. The only thing that's missing is to let new users sign up.

Open 'routes/auth.js' and add the following route at line 52, below the '/logout' route:

router.get('/signup', function(req, res, next) {
  res.render('signup');
});

This route will render the signup page. Let's add an HTML form to that page.

Open 'views/signup.ejs' and add the form at line 15, below the "Sign up" heading:

<h1>Sign up</h1>
<form action="/signup" method="post">
    <section>
        <label for="username">Username</label>
        <input id="username" name="username" type="text" autocomplete="username" required>
    </section>
    <section>
        <label for="new-password">Password</label>
        <input id="new-password" name="password" type="password" autocomplete="new-password" required>
    </section>
    <button type="submit">Sign up</button>
</form>

Finally, we need to add a route that will process the form submission when the user clicks "Sign up." Back within 'routes/auth.js', add this route at line 56, below the '/signup' route:

router.post('/signup', function(req, res, next) {
  var salt = crypto.randomBytes(16);
  crypto.pbkdf2(req.body.password, salt, 310000, 32, 'sha256', function(err, hashedPassword) {
    if (err) { return next(err); }
    db.run('INSERT INTO users (username, hashed_password, salt) VALUES (?, ?, ?)', [
      req.body.username,
      hashedPassword,
      salt
    ], function(err) {
      if (err) { return next(err); }
      var user = {
        id: this.lastID,
        username: req.body.username
      };
      req.login(user, function(err) {
        if (err) { return next(err); }
        res.redirect('/');
      });
    });
  });
});

This route creates a new user record in the app's database, storing the username and hashed password. Once the record is created, the user is logged in.

Return to the app and create a new account.

That's it! Users can now sign in, sign out, and sign up!

SEARCH FOR STRATEGIES

0STRATEGIES