How to add password reset in your LoopBack 4 application
In this tutorial, I will show you steps of how to add password-reset functionality in your LoopBack 4 application (referred to as LB4 from this point) that has JWT authentication enabled.
The password-reset functionality will be split into 2 parts: (a) the first part is where a user will submit their email address. If it exists in our database, we will send them a password-reset link with a unique reset key. Otherwise we will deny them the ability to reset their password.
(b) The second part will be where a user with the reset key provides and confirms a new password. If the password meets our security standards and the reset key is valid upon querying from the database, the password will be reset accordingly.
- Nodejs + NPM installed on your machine.
Install LB4 cli by running the following command:
$ npm i -g @loopback/cli
The above command will install command-line tools to help us quickly create artifacts like models and services.
In order to setup password-reset functionality, we will need a working LB4 application with JWT authentication enabled. Fortunately, LB4 cli comes bundled with lots of example projects to help us get started. Let’s use these to speed things up. On your terminal, navigate to the location you would like to store the project in e.g. Projects on your computer and run:
$ lb4 example todo-jwt
The above command will generate a LB4 application with JWT authentication enabled in the folder you have chosen, e.g.
Let’s test our setup in step 2 by:
$ cd loopback4-example-todo-jwt
$ npm start
If all went well you will see the message below on your terminal:
Server is running at http://[::1]:3000
Open http://localhost:3000/explorer/ on your Browser and you will see several endpoints. We will focus on UserController:
Side note: with 3 steps only, LB4 has created a simple TODO list application for us with a JSON file database to store our data. I suggest you wait until the end of the tutorial to utilise the resources provided to get more clarity. However, feel free to pause and explore this repository in detail.
Before we begin adding functionality to the application let’s do few preparations:
- Add LB4 models to represent request payloads and responses
- Add LB4 email send service
- Install the following dependencies:
$ npm i nodemailer # we will use to send email to reset password
$ npm i -D @types/nodemailer # to enable TypeScript support for nodemailer
$ npm i dotenv # to enable support for use of environment variables
$ npm i uuid # to generate a unique reset key
$ npm i -D @types/uuid # to enable TypeScript support for uuid
Using the LB4 cli you can generate models using the following command:
$ lb4 model <ModelName>
We will create all our models beforehand and reference them accordingly. To get started, we will need the following models:
- KeyAndPassword — to represent the user’s password-reset request
- EmailTemplate — to represent the email request template for Nodemailer
- NodeMailer — to represent the response from Nodemailer after sending password-reset email
- Envelope — to represent the envelope portion of the response from Nodemailer after sending password-reset email
- ResetPasswordInit — to represent the request for initial password-reset step
You can also update src/models/index.ts with the exports of these files to make them more accessible in other files.e.g.:
export * from './reset-password-init.model';
export * from './envelope.model';
export * from './node-mailer.model';
export * from './key-and-password.model';
export * from './email-template.model';
To add LB4 service you can run:
$ lb4 service Email
Select Local service class bound to application context when prompted for the type of service you would like to create.
Let’s add more lines to the service class:
In the above, we setup a Nodemailer transporter in lines 12–21. Thereafter, we initialise the configs using environment variables. Next, on the root of the project you will have your SMTP server settings configured in a .env file:
Let’s update the start script to accommodate usage of dotenv. Inside package.json update the start command to “start”: “node -r dotenv/config .”
In line 23 and 39 we use the transporter to send an email based on the email template, as we configured in our model.
Now that we have the models and services in place lets go back to our controller and glue everything together.
In order for users to reset their passwords they will first provide an email address that we will validate and check against the database whether there is a user with such an email address. Let’s go ahead and start building the initial password-reset steps:
We first need to bring the service into the controller by adding the following line to the constructor of the User controller:
The above line tells LB4 that we want to use our service in the controller thus we task LB4 to scan services folder to locate a class called EmailService. Once scanned LB4 will make available all the functionality that exists inside EmailService into the UserController. This concept is known as Dependency Injection (DI). To learn more about LB4 DI see further reading links at the end of the tutorial.
Next, we add a skeleton function responsible for handling the initial password reset logic:
The above snippet creates an HTTP POST endpoint with URI /reset-password/init. The endpoint expects a request payload object of RestPasswordInit (with email property) and returns a Promise of string.
Let’s start the application and navigate to http://localhost:3000/explorer. You will notice a new endpoint, as illustrated below:
Lets add few more lines this time around with comments to help us understand each line:
Restart the application and navigate to http://localhost:3000/explorer. Click on the reset-password-init endpoint and click on Try it out button. Enter an email and click execute (notice the curl POST):
If all of your configuration is valid you should see the response body such as the one in the image above.
If you used a valid email you will see the below email in your inbox:
Let’s trigger the email validation:
Let’s test with a non-existing email account:
Now that we have the first part working as expected let’s add the second part (completing the password-reset workflow).
To this point we have enabled a registered user to receive a password-reset link in their inbox. Let’s look at how we can build another endpoint to enable the user to complete their password-reset request.
In User controller we add another endpoint as per below (code comments inline per statement):
The above code snippet will result in a new endpoint, as shown below. Let’s capture the reset key and password details:
To reset password with a valid reset key check inbox for the reset key sent as part of the password-reset link. Enter the reset key and passwords and click execute:
From the above, we can see that the user’s password has been successfully reset, as confirmed by the response. You can also confirm that the reset key has been reset by opening data/db.json and locating the resetKey field. This should be an empty string. Additionally, the userCredentials object should have also changed inside data/db.json although encrypted.
Let’s test what happens when we provide password that does match confirmation password:
Let’s test what happens when we provide invalid reset key:
This tutorial discussed how to add password-reset functionality in a LoopBack 4 application. We took a look at how we can use existing LoopBack examples to get started quickly. Furthermore, we created models and services to handle data representation and email sending logic. Finally, we explored how to set up 2 endpoints in a controller to allow a user to reset their password. The final source code is available at:
Looking at our password-reset functionality we are missing few key things:
- How do we ensure the reset key is valid for a short period of time?
- How do we limit a hacker with the user’s email from sending too many password-reset requests to the user’s inbox?
We will look at these in another article to be linked here.
LoopBack 4 is an awesome framework with lots of features that have not been covered in-depth in this article. In future articles, we will uncover these. Thank you for your time, and please feel free to explore some of the topics discussed here in detail in the further reading section.