Password Workflows With NodeJS on AWS

Nikolas Osvalds
Oct 19, 2020 · 6 min read

Implementing a secure “forgot password” and “reset password” backend (for a Vue.js front end) in NodeJS with AWS Lambda, SES, and DynamoDB.

Background

I volunteer with DigitalHumani’s Reforestation as a Service API project which connects websites and mobile apps to trusted reforestation organizations to have trees planted for $1 when a user action is taken. The MVP was built by volunteers and continues to be maintained and improved by a small (but growing) team.

The team is working on a new secure dashboard website that will be used by clients organisations to view information about the number of trees their users have requested to be planted as well as by the DigitalHumani team to administer the API.

I tasked myself with designing and implementing the forgot password and reset password backends. A basic dashboard had already been created with the ability to register and log in using passport-jwt.

Workflow Steps

Descriptions in non technically detailed language to illustrate what we’re trying to implement.

Forgot Password

  • On login page, user clicks the forgot password link
  • They are taken to a form where they are prompted to enter their email and click submit
  • User receives an email with a URL
  • User navigates to the URL and enters new password (twice to confirm), then submits the form
  • The new password is saved and the user can login with their new password.

Reset Password

  • User logs in and navigates to the User Account page
  • User enters their existing password and new password (twice to confirm), then submits form
  • Password is updated for the user

Implementation

API Routes

The backend of this application is implemented in NodeJS using the Express library. Here are the definitions in the main index.js file.

POST /auth/forgot-password

The forgot-password route takes the user’s email address from the front end in a POST request and calls the AuthController.forgotPassword function.

Request format (JSON)

The forgotPassword function does several things:

  • Validation that there is a user associated with the email provided
  • Expires any existing password reset tokens for the user to prevent old tokens from being used
  • Creates a new password reset token entry in the database
  • Generates an email with the URL that the user will use to reset their password

Password Reset Tokens w/ DynamoDB

So what are these password reset tokens I’m referring to? Well the idea here is that when the password reset request is made, you generate a new token entry in the database. This consists of a long random string and some metadata about the “token”. This long random string is then used on the /auth/reset-password route to ensure the request to update the password is legitimate.

In this case I decided to store the password reset token entries in the User table rather than using a separate DynamoDB table (a separate table seems common in the SQL world and is standard in frameworks like Laravel ). The benefits of a single table for DynamoDB in my case included a reduced number of queries to the database and less overhead of maintaining an additional table. You can read in great detail here about the benefits of single table design in DynamoDB.

In my case the User table was already defined from the previous work on the dashboard. I’ve pulled out the pertinent sections from the serverless.yml file here. Main points worth noting here are that the index.js is compiled into a Lambda function and the hash key for the user table is the email attribute.

Expiring existing password tokens

Working with DyanmoDB

I found that it was much easier to work with the data I needed to update as an object rather than trying to work with the DynamoDB UpdateExpressions to target a specific set of items. For example with expiring the existing password tokens, I grab the user.password_reset_token object and loop through that setting .used item to true to expire the tokens. Then update the entire user.password_reset_token item in DynamoDB.

Email components

The most important piece of the email is the URL that will take the user to the front end form to enter a new password. It must contain the correct domain and route of your front end as well as 2 query parameters, the email and the password reset token. The front end will need these parameters to send to the /auth/reset-password route along with the new password so that the backend can validate the token and update the password.

URL format and example

http(s)://domain/user/reset-password?token='<token>'&email='<email>'

Populating the email

To fill the email with the dynamic data (e.g. url, username, expiration date, etc.) I used the Handlebars library to inject the emailData variables into the forgotPasswordEmail.html email template I based off of mailchimp’s opensource email-blueprints. In your html you wrap the variables you’d like to replace in curly braces {{ variable }}. See below for the body of the email:

Sending the Email

The parameters for the email are then built up in the params object in the required format for Amazon’s Simple Email Service SES. I won’t go into detail here as there are a lot of good tutorials out there on how to send emails with SES in NodeJS.

POST /auth/reset-password

The reset-password endpoint takes the user’s email address, password reset token, and the new password from the front end in a POST request and calls the AuthController.resetPassword function.

Request format (JSON)

This function does several things:

  • Validation of the new password
  • Looks up token provided in the User table and consumes it if valid
  • Sets the new password for the user

The bulk of the logic is in the User.usePasswordToken() function.

The hashed password is generated using a utility function which utilizes the bcryptjs library.

At this point the user has now successfully updated their password!

Now what if they haven’t forgotten their password and just want to update it to something more secure or they suspect it’s been compromised.? That’s where the update-password route comes in.

POST /auth/update-password

The update-password endpoint takes the user’s email address, current password, and new password (twice to confirm they match) from the front end in a POST request and calls the AuthController.updatePassword function.

You may have noticed the additional argument provided in this route passport.authenticate(‘jwt’, { session: false }). This is passport JWT middleware which will enforce the requirement that a user be in a valid authenticated dashboard session to change their password.

Request format (JSON)

This function does several things:

  • Validates that there is a user associated with the email provided
  • Validates the current password is correct for the user
  • Validates the new password format and that they match
  • Sets the new password for the user

With this complete we have a functioning backend for all of our password reseting and updating workflows. I hope this helped you with making your own password workflows with NodeJS and DynamoDB!

Further Improvement Ideas

  • Create a cron job to clean up old/expired password reset token entries
  • Design a report that uses the data stored in the password reset token entries

Resources

I used this great article as a guide, but was pretty on my own for the DynamoDB pieces.

Additionally OWASP has a nice Forgot Password cheat sheet that was relevant.

If you’re interested in learning the serverless framework I’d highly recommend Complete Coding’s video series.

Check out my other projects

The Startup

Medium's largest active publication, followed by +775K people. Follow to join our community.

 by the author.

Nikolas Osvalds

Written by

I’m passionate about doing good, giving back, and helping to tackle the climate crisis with my working life, ideally with code.

The Startup

Medium's largest active publication, followed by +775K people. Follow to join our community.

Nikolas Osvalds

Written by

I’m passionate about doing good, giving back, and helping to tackle the climate crisis with my working life, ideally with code.

The Startup

Medium's largest active publication, followed by +775K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store