How to Set Up Passport Authentication in a Node and Postgres Application
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.
Nội Dung Chính
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:
- To check if the email is already registered.
- 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(
)
);
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.