As a developer, it is your responsibility to safeguard your users’ data through authentication. You can use Passport.js to authenticate users in a Node and Postgres application.

Start by creating a Node server with endpoints to register, sign in, and sign out users. You can let Passport handle authentication to restrict unauthorized access to your application.

Creating a Users Table

For user authentication, you will use an email and a password. This means the users table must contain an email and a password field. In the psql command prompt, create a new database called nodeapp:

        CREATE DATABASE nodeapp;

Next, create a table to store the users:

        CREATE TABLE users (
 id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
 email CHAR(128),
 password CHAR(60)
);

This code will create a new table containing email, password, and an auto-generated ID field.

Creating a Node Server

Node.js is a server-side JavaScript runtime environment that allows us to create HTTP servers quickly. To simplify the process of creating the server and different HTTP routes, you can use Express, a Node.js web framework.

Run this command to create a new folder called postgres-auth:

        mkdir postgres-auth

Next, initialize npm:

        npm init -y

Finally, install Express:

        npm install express

You can now create the Node web server.

In a new file called index.js, add the following:

        const express = require("express");
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.listen(3000, () => console.log("Listening on port 3000"));

Running this code will start the server and log the following in the console:

        Listening on port 3000
    

Connecting to PostgreSQL

To connect to PostgreSQL use node-postgres. node-postgres is a connection driver that provides an interface between Node and Postgres.

Execute the following to install node-postrges via npm:

        npm install pg

Once you’ve installed that library, create a new file called db.js and connect it to the database:

        const { Client } = require("pg");
const { user, host, database, password, port } = require("./dbConfig");
 
const client = new Client({
  user,
  host,
  database,
  password,
  port,
});
 
client.connect();

module.exports = client;

The client method from node-postgres takes the details of the database you are connecting to. This program imports its connection details from a file called dbConfig. Therefore, create that file and add the following code to it:

        module.exports = {
  user: "postgres",
  host: "localhost",
  database: "nodeapp",
  password: "yourPassword",
  port: 5432,
};

Create Database Helper Functions

It’s always good practice to use individual functions to interact with the database. They make it easy to write unit tests and improve reusability. For the signup endpoint, you need to create two functions:

  1. To check if the email is already registered.
  2. To create the user.

The goal is to only register a user if they don’t exist in the database.

Create a new file called helper.js and import the database client from db.js:

        const client = require("./db.js")

Next, add a new function called emailExists():

        const emailExists = async (email) => {
  const data = await client.query("SELECT * FROM users WHERE email=$1", [
    email,
  ]);
 
  if (data.rowCount == 0) return false;
  return data.rows[0];
};

This function takes an email and checks if it is already in use. It does this by using the SELECT clause that returns a row that has an email field that matches the value provided by the registering user. If the email does not exist, it returns false.

To create a function that creates the user, add a function called createUser() to helper.js:

        const createUser = async (email, password) => {
  const salt = await bcrypt.genSalt(10);
  const hash = await bcrypt.hash(password, salt);
 
  const data = await client.query(
    "INSERT INTO users(email, password) VALUES ($1, $2) RETURNING id, email, password",
    [email, hash]
  );
 
  if (data.rowCount == 0) return false;
  return data.rows[0];
};

This function takes the email and password values. It uses the INSERT clause to create a new row with these details and if successful returns the newly created user. Note that before storing the password, you should hash it using bcrypt. It is never a good idea to store passwords as plain text. If hackers got access to your user database they could easily access sensitive information.

Install bcryptjs to start using it:

        npm install bcryptjs

In helper.js, import bcryptjs:

        const bcrypt = require("bcryptjs")

By using Bcryptjs, the database only stores the encrypted password. Therefore, during login, you will need to compare the plain text password given by the user and the hashed password in the database. For this, you can use the compare method provided by Bcryptjs.

Create a function called matchPassword():

        const matchPassword = async (password, hashPassword) => {
  const match = await bcrypt.compare(password, hashPassword);
  return match
};

It receives the plain password and the hash and then uses Bcrypt.compare() to determine if the password provided is correct. If it is, it returns true otherwise, it returns false.

These are all the functions we will use to interact with the database. Make sure to export all of them at the end:

        module.exports = { emailExists, createUser, matchPassword };

Configure Passport

Passport is a Node authentication middleware that provides over 500 authentication strategies like social login, JSON Web Tokens (JWT), and email authentication. We will be using the latter which the passport-local strategy provides.

Use the following command to install passport and passport-local:

        npm install passport
npm install passport-local

Next, configure Passport to login existing users and register new users.

Start by creating a new file passportConfig.js. Then, import the Passport local strategy and the database helper functions you just created:

        const LocalStrategy = require("passport-local");
const { emailExists, createUser, matchPassword } = require("./helper");

In the same file add the following to set up user sign-up:

        module.exports = (passport) => {
  passport.use(
    "local-signup",
    new LocalStrategy(
      {
        usernameField: "email",
        passwordField: "password",
      },
      async (email, password, done) => {
        try {
          const userExists = await emailExists(email)
 
          if (userExists) {
            return done(null, false);
          }
 
          const user = await createUser(email, password);
          return done(null, user);
        } catch (error) {
          done(error);
        }
      }
    )
  );
}

Since passport-local expects a username and a password, and you are using an email, set the username field to an email. The user or rather the frontend part of this application will send the email and password in the request body. However, you don’t need to extract the values yourself as Passport will handle that in the background.

This program first checks whether the email is already taken using the emailExists() function from helper.js. If the email does not exist in the database, it creates a new user with the createUser() function. Finally, it returns the user object.

To login users, add the following to passportConfig.js:

        module.exports = (passport) => {
  passport.use(
    "local-signup",
    new LocalStrategy(
// sign up
    )
  );
  passport.use(
    "local-login",
    new LocalStrategy(
      {
        usernameField: "email",
        passwordField: "password",
      },
      async (email, password, done) => {
        try {
          const user = await emailExists(email);
          if (!user) return done(null, false);
          const isMatch = await matchPassword(password, user.password);
          if (!isMatch) return done(null, false);
          return done(null, {id: user.id, email: user.email});
        } catch (error) {
          return done(error, false);
        }
      }
    )
  );
};

Here, the program first checks whether the email is registered. If not, it returns false. If it finds the email, it compares its password with the one from the request. If the passwords match, it logs in the user and returns the user object.

The final step is to create the API endpoints:

  • POST /auth/signup
  • POST /auth/login

Both of these endpoints will receive an email and password in the request body. They will also include the passport authentication middleware functions we just configured.

Import and set up Passport in a new file named server.js:

        const passport = require("passport");
require("./passportConfig")(passport);

Then, add the following routes:

        app.post(
  "/auth/signup",
  passport.authenticate("local-signup", { session: false }),
  (req, res, next) => {
    res.json({
      user: req.user,
    });
  }
);

app.post(
  "/auth/login",
  passport.authenticate("local-login", { session: false }),
  (req, res, next) => {
    res.json({ user: req.user });
  }
);

Both of these routes return a JSON object containing the user if successful.

Check Your API Using Unit Tests

You can use Passport to authenticate a Node application using a PostgreSQL application. You created API endpoints to sign up and log in users.

While you can use REST clients like Postman to test how well an API works, writing unit tests is much simpler. Unit tests allow you to test the individual parts of your application. This way, even if an endpoint fails, you can pinpoint the exact point of failure. One of the tools you can use to test Node applications is Jest.