Forgot Password using Node JS with Knex and Express

A detailed analysis of how to make a reset for a user when he has forgotten his password. How to send email from Node JS and validate sending messages.

Anton Kalik
ITNEXT

--

Forgot Password on Node JS with Knex and Express
Photo by Kelly Sikkema on Unsplash

Most of us have experienced the account recovery process at least once — when we forget a password, there’s a need for procedures to create a new one and regain access to the system. This article focuses on implementing such a process using Node.js, Knex, and some undisclosed tools, alongside Express to handle routes and perform the necessary operations. We’ll cover the router implementation, handling URL parameters, determining what to send to the user when only an email or phone number is available as proof, managing email submissions, and addressing security concerns.

Forgot Password Flow

Before diving into coding, I’d like to ensure that we’re working with the same codebase, which you can access from my public repository on GitHub. We will upgrade step by step to implement the forgot password flow. For email transport, we will utilize Google’s email service.

Now, take a look at the schema of the forgot password flow.

Schema forgot password flow
Schema forgot password flow

The server will be responsible for sending emails to the user mailbox containing a valid link for password resetting, and will also validate the token and user existence.

Packages and Migration

To begin utilizing the email service and sending emails with Node.js, we need to install the following packages in addition to our existing dependencies:

npm i --save nodemailer handlebars

Nodemailer: Powerful module that allows to send emails easily using SMTP or other transport mechanisms.

Handlebars: Handlebars is a popular templating engine for JavaScript. It will allow us to define templates with placeholders that can be filled with data when rendering.

Now we need to create the migration, so in my case, I have to add a new column forgot_password_token to users table:

knex migrate:make add_field_forgot_password_token -x ts

and in the generated file, I set the code:

Migration for Forgot Password Token in Users table

and then migrate the latest file:

knex migrate:knex

So now we can set to the users table our forgot_password_token

Routers

To manage controllers responsible for handling the logic of password forgetfulness and resetting, we must establish two routes. The first route initiates the forgot password process, while the second handles the reset process, expecting a token parameter in the URL for verification. To implement this, create a file named forgotPasswordRouter.ts within the src/routes/ directory and insert the following code:

Forgot Password Router

Two controllers will manage the logic for sending emails and resetting the password.

Forgot Password Controller

When the client forgets his password he has no session, which means we can’t get user data except email or any other security identifiers. In our case, we are sending an email to handle a password reset. That logic we going to set into the controller.

forgotPasswordRouter.post('/', forgotPasswordController);

Remember the ‘forgot password?’ link below the login form usually in UI of any clients in the login form? Clicking on it directs us to a view where we can request a password reset. We simply input our email, and the controller handles all the necessary procedures. Let’s examine the following code:

Forgot Password Controller

From the body, we going to get an email, and then we will find the user using UserModel.findByEmail. If the user exists, we create a JWT token using TokenService.sign and save the token to the user forgot_password_token with an expiration of 1 day. Then we will send the message to the email with a proper link together with a token where the user will be able to change his password.

Google Setup

To be able to send email we have to create our new email address which will be a sender.

Let’s go to Google, to create a new email account, and then, when the account is created, proceed to the Manage your Google Account link. You can find it on the top right by clicking on avatar. Then on the left menu click on the Security item and then press 2-Step Verification. Below you will find the App passwords section, click on the arrow:

App Passwords

Input the name that need to use, in my case I set Nodemailer and press Create.

App Passsords
Create App Password

Copy the generated password and set it to your .env file. We need to set to file two variables:

MAIL_USER="mygoogleemail@gmail.com"
MAIL_PASSWORD="vyew hzek avty iwst"

Of course, to have a proper email like info@company_name.com you have to set up Google Workspace, or AWS Amazon WorkMail together with AWS SES, or any other services. But in our case, we are using a simple Gmail account for free.

Email Service

With the .env file prepared, we are ready to set up our service for sending emails. The controller will provide the service with the generated token and the recipient email address for our message.

await EmailService.sendPasswordResetEmail(email, token);

Let’s define the class for the service:

export class EmailService {}

and now as initial data, I have to get the environment to use with nodemailer:

Email Service

We have to take care of service initialization. I was writing about it before in my previous article. Here is an example:

Initializing Services

Now, let’s proceed with creating the initialization within our EmailService class:

Email Service Initialization

There is initialization nodemailer.createTransport() a method provided by the nodemailer library. It creates a transporter object that will be used to send our emails. The method accepts an options object as an argument, where you specify the configuration details for the transporter.

We are using Google: service: 'gmail' specifies the email service provider. Nodemailer provides built-in support for various email service providers, and gmail indicates that the transporter will be configured to work with Gmail's SMTP server.

For authentication auth it’s necessary to set the credentials which required to access the email service provider's SMTP server. For user should be set to the email address from which we are going to send emails, and that password has been generated in Google account from App Passwords.

Now, let’s set the last part of our service:

Send Password Reset Email

Before proceeding, it’s crucial to determine the appropriate host for when the client receives an email. Establishing a link with a token in the email body is essential.

Get Host

For templates, I am using handlebarsand for that, we need to create in src/temlates/passwordResetTemplate.hbs our first HTML template:

Password Reset Template

and now we can reuse this template with the helper:

Generate Template Helper

To enhance our email, we can even include attachments. To do so, add the email_logo.png file to the src/assets folder. We can then render this image within the email using the following helper function:

Generate Attachments Helper

After collecting all of those helpers, we have to be able to send email using:

const info = await EmailService.transporter.sendMail({
from: this.env.USER,
to: email,
subject: 'Password Reset',
html: template,
attachments,
});

This approach offers decent scalability, enabling the service to employ various methods for sending emails with diverse content.

Now, let’s try to trigger the controller with our router and send the email. For that, I am using Postman:

Postman Screen Send Email
Send Email

The console will tell you that the message has been sent:

Message sent: <1k96ah55-c09t-p9k2–8bv2-j25r9h77f763@gmail.com>

Check new messages in the inbox:

New Email in the Inbox
Inbox with a new email from the server

The link to Reset Password has to contain the token and host:

http://localhost:3000/reset-password/<token>

The port 3000 is specified here because this message pertains to the development process. This indicates that the client responsible for handling forms for password reset will also be operating within the development environment.

Reset Password

The token has to be validated on the controller side with TokenService from where we can get the user who sent that email. Let’s recover the router which uses the token:

forgotPasswordRouter.post('/reset/:token', resetPasswordController);

The controller will only update the password if the token is valid and not expired, as per the expiration time set to one hour. To implement this functionality, navigate to the src/controllers/ folder and create a file named resetPasswordController.ts containing the following code:

Reset Password Controller

This controller will receive the token, verify it, extract the user ID from the decrypted data, retrieve the corresponding user, acquire the new password sent by the client in the request body, and proceed to update the password in the database. Ultimately, this enables the client to log in using the new password.

Conclusion

The scalability of the email service is demonstrated through various approaches, such as sending confirmations or success messages, like those indicating a password update and enabling subsequent login. However, managing passwords is a great challenge, particularly when enhancing application security is imperative. There are numerous options available to bolster security, including additional checks before permitting password changes, such as token comparison, email, and password validation. Another option is to implement a PIN code system, where a code is sent to the user’s email for validation on the server side. Each of these measures necessitates the utilization of email-sending capabilities.

All implemented code you can find in GitHub repository here.

Please feel free to conduct any experiments with this build and share your feedback on what aspects you appreciate about this topic. Thank you so much.

References

Here, you can find several references that I utilized in this article:

--

--