Have you ever wondered how to implement a reset password functionality on the server with NodeJS and PostgreSQL?
I found many articles and tutorials on how to do it with Node and MongoDB, here I'm going to tell you how I've done it with PostgreSQL.
First, we need to find an email service, I've tried Nodemailer at first and it works perfectly in my local environment, however, in production I was getting some security issues with my Gmail account. That's why I decided to go with SendGrid.
Create a SendGrid account, go to Email API, Integration Guide, choose Web API and just follow instructions to generate an API Key
Install dependencies
npm install --save @sendgrid/mail
npm install express bcryptjs jsonwebtoken knex
Now that you have created the API key, create a .env file to store your key.
Let's see how our user table looks
I'm using the Knex library to interact with the database
table.increments();
table.string("email", 128).unique().notNullable();
table.string("password", 128).notNullable();
table.string("resetLink", 255);
As you see we define "resetLink"
to be optional.
Now let's create the routes
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const sgMail = require('@sendgrid/mail');
const route = express.Router();
// bring env variables into file
const sendGridKey = process.env.SENDGRID_KEY;
const resetSecret = process.env.RESET_SECRET;
route.patch('/forgot-password', async (req, res) => {
const { email } = req.body;
try {
// look for email in database
const [user] = await filterBy({ email });
// if there is no user send back an error
if(!user) {
res.status(404).json({ error: "Invalid email" });
} else {
// otherwise we need to create a temporary token that expires in 10 mins
const resetLink = jwt.sign({ user: user.email },
resetSecret, { expiresIn: '10m' });
// update resetLink property to be the temporary token and then send email
await update(user.id, { resetLink });
// we'll define this function below
sendEmail(user, resetLink);
res.status(200).json({ message: "Check your email"} );
}
} catch(error) {
res.status(500).json({ message: error.message });
}
}
Let's define the new PATCH
route to update the new password
route.patch('/reset-password/:token', async (req, res) => {
// Get the token from params
const resetLink = req.params.token;
const newPassword = req.body;
// if there is a token we need to decoded and check for no errors
if(resetLink) {
jwt.verify(resetLink, resetPassword, (error, decodedToken) => {
if(error) {
res.status().json({ message: 'Incorrect token or expired' })
}
})
}
try {
// find user by the temporary token we stored earlier
const [user] = await filterBy({ resetLink });
// if there is no user, send back an error
if(!user) {
res.status(400).json({ message: 'We could not find a match for this link' });
}
// otherwise we need to hash the new password before saving it in the database
const hashPassword = bcrypt.hashSync(newPassword.password, 8);
newPassword.password = hashPassword;
// update user credentials and remove the temporary link from database before saving
const updatedCredentials = {
password: newPassword.password,
resetLink: null
}
await update(user.id, updatedCredentials);
res.status(200).json({ message: 'Password updated' });
} catch (error) {
res.status(500).json({ message: error.message });
}
})
Let's also define the functions we are using to interact with the database in this case is
findBy() and update()
// I'm using knex to interact with the database here
// but all is doing is looking into 'users' table and filtering the data by the filter parameter
function filterBy(filter) {
return db('users').where(filter);
}
// same here, looking into 'users' table by 'id' and then updating the values
function update(id, changes) {
return db('users').where({ id }).update(changes);
}
Let's define sendEmail
function
function sendEmail(user, token) {
sgMail.setApiKey(sendGridKey);
const msg = {
to: user.email,
from: "", // your email
subject: "Reset password requested",
html: `
<a href="${clientURL}/reset-password/${token}">${token}</a>
`
// I'm only going to use an (a tag) to make this easier to
// understand but feel free to add any email templates
// in the `html` property
};
sgMail.send(msg)
.then(() => {
console.log("Email sent");
}).catch((error) => {
console.error(error);
})
}
Now that we've created the routes I'm going to use Postman to test it, we need to make a PATCH
request
You should get a 200 status if everything is okay, check your email and copy the token from the email
On Postman paste the token url/auth/reset-password/:token
see example below
You should get a 200 status code with a Password update
message
Here is a link to one of my repos with full example code repo
I hope you find this helpful! If you have any questions let me know in the comments.
I'm planning to add the same functionality but using Redis to store the temporary token instead of the database.
Top comments (4)
The blog owner seems to complicate things too much for beginners. It's not easily understandable for someone just starting out.
Hello,please do you have the local one you Implemented with nodemailer...
blog.logrocket.com/implementing-se...
Helpful a lot!