React Native: Secure Password Reset

How to implement secure password reset in-app with email verification

Image for post
Image for post

A password reset mechanism is a critical part of any app that requires authentication, as a means of letting a user back into their account if they forget their credentials. The implementation of a password reset system will have an impact on the overall security of your app, depending on the verification process required to reset the password. Password resetting is no trivial thing, and should be carefully considered before being included with an app.

This article will focus on a React Native implementation of a reset password workflow that has been designed to interact with a Node Express server to send a random code to the requester’s email address as the user verification method. More specifically, we will:

  • Summarise the project’s React Native demo (available on Github) and explain the password reset process in detail
  • Overview the project’s source code, exploring how the reset forms have been structured and imported, and switched between using useState as the user progress through the password reset stages.
  • How to handle the random number generation when a user requests a reset code. As this is done on the backend, we’ll visit an Express based script to achieve this, in conjunction with Mailgun API — a reliable email service with a generous free tier for developers to experiment with.

Since writing this article Mailgun have switched to a new pricing model that allows new accounts 5,000 free emails per month for 3 months.

The demo project has been designed to act as boilerplate to be built upon for other React Native projects. These scripts will be TypeScript based, but they can also be refactored for plain Javascript, too.

To understand authentication flow in React Native in a more general manner, refer to my previously published article, React Native: User Authentication Flow Explained.

Project Demo

The demo project accompanying this piece showcases the screen flow of the password reset process, and can be found here on Github. What we will explore here is an automated solution — no human is required to intervene for the password reset to take place. However, depending on the sensitivity of your application’s data, this will not always be the case.

The following screencast summarises the project, that provides plug-in-and-play boilerplate for a password reset solution:

Image for post
Image for post

To play with the project on your machine, simply clone the repository and run yarn start to get it going. The project is bootstrapped with Expo, also requiring expo-cli to be installed:

// project installationyarn global add expo-cligit clone https://github.com/rossbulat/rn-password-reset.git
cd rn-password-reset && yarn && yarn start

This demo does not contact a server, currently only using setTimeout to mimic server behaviour. We will however cover the backend scripts to generate the random reset code further down the article.

Before diving into the project source code, lets firstly recap what allows a password reset to securely take place.

Understanding Password Reset Behaviour

Password resets often pertain to the user proving who they are, by verifying un-guessable data sent from the app server to a secure source the user owns, such as their phone or email address. This is by and large the standard way of resetting a password for apps and websites, with the additional step of verifying other account information, such as secret questions and answers or the last digits of a credit card, to initiate the password reset.

Often times your app database will not store sensitive data such as payment methods, so requesting the last digits of the phone number or primary email address used for the app could be adopted instead to initiate the reset request. Requiring additional account information effectively stops password reset attempts by random people who may only have one piece of information, such as a user’s email address.

Email addresses are public, and therefore easy to guess. Having your app ask for additional information to verify adds an extra layer of protection before a reset request is initiated.

If the user cannot verify this requested data, such as if they no longer have access to their email address or phone number, then a manual process is often used as a fallback where an app support team often requests proof of ID of the user, before going ahead with the password request.

Although this is sometimes necessary, it is in the interest of both the user and app team to avoid the manual process as to save time for both parties. If you are dealing with financial products such as banking, trading or investment applications, this process will likely be mandatory for all reset attempts. For casual apps though, we’ll just explore a standard automated password reset workflow.

With this in mind, lets overview password reset tailored for React Native.

React Native Project Overview

Even though the 3-step process may look simple, it actually comprises of 3 separate forms, managed by formik.

To learn more about Formik, I have written a range of other pieces on the form manager. For Formik used with React Native, check out React Native Form Management: An Introduction:

The project also hosts a range of custom components that wrap certain form elements to simplify the building process. These can be found in the Form folder, that includes components such as ErrorMessage, TextInputWithIcon and InputLabel.

Diving into the conventions used for the project’s components is out of the scope of this particular article, but I’ll be sure to link further readings here as they are published.

The three forms that comprise the password reset process are hosted in one screen, and switched between using component state. Although you could indeed embed each form into a stack navigator using react-navigation, we’ve opted to avoid the package as to not add complexity to the project. It is also useful to have all the forms imported in one file, making the process more manageable.

This setup is quite self explanatory when examining the project’s ResetPassword/ folder.

ResetPassword would also be the component you would hook up to a react-navigation screen.

The index.tsx file itself acts as the entry point. To keep things manageable, each form is imported from its own component, each living in the same ResetPassword/ folder:

import { ResetForm } from './ResetForm'
import { CodeForm } from './CodeForm'
import { NewPasswordForm } from './NewPasswordForm'

Once imported, each form represents a particular stage of the reset process that correspond to a stages enum value:

// defining the three `stages`enum stages {
REQUEST_LINK = 'REQUEST_LINK',
VERIFY = 'VERIFY',
RESET = 'RESET',
}

In addition, the currently active stage is managed through stage, with the useState hook referring to the currently active stage through the stages enum:

// active stage through local stateconst [stage, setStage] = useState(stages.REQUEST_LINK);

The active stage is then displayed based on this state value. The following pseudo code demonstrates the general setup of the component:

// project structure pseudo code<App>
<ResetPassword>

const [stage] = useState(1);
stage === 1 && <ResetForm />
stage === 2 && <CodeForm />
stage === 3 && <NewPasswordForm />
</ResetPassword>
</App>

We can now leverage setStage, that is wrapped in a separate switchStage() function and passed into each of the 3 forms as a prop. From here, the correct form is displayed based on the current stage value:

const switchStage = (stage: stages) => {
setStage(stage);
}
...return (
{stage === stages.REQUEST_LINK &&
<ResetForm
stages={stages}
switchStage={switchStage}
setEmail={setEmail}
/>
}
...
);

Could we just pass setStage as a prop instead? Yes, perfectly valid. However, I find it is good practice to wrap setters, that can host things like debugging statements and additional conditionals where needed.

In the scenario you wish to take values from a submitted form and store them in state, we can define additional useState hooks and pass them as props into our form components. You may notice that we are doing exactly this to keep track of the submitted email address (in stage 1), with setEmail.

const [email, setEmail] = useState(null);

Why would we need to remember the email address? Well, once the user has submitted a new password with the 3rd form, the email address they verified in stage 2 can be used as their account unique identifier in order to update their password. To think of this in as a database query, email can be used as the UID to update the password field.

We can actually see that email is being passed as a prop into <NewPasswordForm /> , becoming available to API fetch requests when the new password is submitted:

{stage === stages.RESET &&
<NewPasswordForm
stages={stages}
switchStage={switchStage}
email={email}
/>
}

Once a form is submitted, switchStage() is called within the form’s onSubmit handler function. We can see this clearly in the ResetForm component (stage 1) as the user user submits their email address:

//ResetForm's onSubmit handleronSubmit={(values, formikActions) => {
this.setState({
submitting: true,
serverError: null
});
// server validation in place of `setTimeout`
setTimeout(() => {
formikActions.setSubmitting(false);
this.props.setEmail(values.email);
this.props.switchStage(this.props.stages.VERIFY);
}, 1000);

Once called, the parent component’s state will update with the provided stage, causing a re-render with its corresponding form. This is effectively what creates the password reset flow until the process has been completed:

Image for post
Image for post
Jumping between stages in the reset password flow

With this conceptual understanding the reader should be able to browse the project with the understanding of the component structure and how they link together.

What the React Native side of the project does not demonstrate is how the random code is generated server-side — let’s visit how this is done next.

Random Code Generation

Generating an un-guessable random code is a key part of an authentication system — and there are a few tools at our disposal to generate such a code within a Node JS Express backend environment.

Let’s say that a password reset request lands at a auth/password-reset-request route, giving us the server to handle the request accordingly. Breaking down what this route should process:

  • Verify that a valid email has been sent in req.body, along with fetching the ip address of the sender for more understanding about where the request came from
  • Check that the account exists in your database, and return the request if it does not.
  • Generate the random code and store it, along with an expiry timestamp, in your database
  • Send an email to the user containing the code to verify in-app
  • Return a JSON response of either a success or failure

The route itself will most likely need to be asynchronous so we can await database queries and other promise based functions:

// define the route as asynchronousrouter.post('/password-reset-request', async function (req, res, next) { 
...

Fetching the request body for the email address, along with the ip address from the request headers, should be done at the top of the route block:

// get request body and headersconst { body } = req;
const { email } = body;
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

You may wish to send additional headers to your backend to prove the requester’s authenticity, along with ensuring that all requests are sent to an encrypted endpoint. Access the request headers with req.headers.

You’ll want to validate this incoming data straight away, and return a response in the case the data is not valid. In order to do this, you may be tempted to simply return a JSON response like so:

res.json({
ack: 'failure',
reason: 'Could not verify email address',
})

Doing this is valid syntax, but we can be more systematic in how we handle responses, as the route will be returning response objects in a range of places. Let’s briefly explore how we can organise our API responses.

It’s very good practice to move away from hard coding response codes, and move towards immutable constants instead. To do so you could simply define a new module, constants.js, in your Express project directory:

Defining constants in ./constants.js

Notice that we have wrapped the module exports with Object.freeze, that ensures that none of the properties defined within can be changed, added to or removed. This effectively provides immutable constant values to be used in your routes. API_SUCCESS and API_FAILURE are the two codes we’ll demonstrate here, but the module can be easily expanded to suit your API needs.

Importing constants can then be used with the common require syntax:

var constants = require('./constants')console.log(constants.API_SUCCESS);

To give more organisation to responses themselves, another good practice is to define response structures within another module. Let’s do just that with a success and failure response, passing in the route’s res argument, along with a custom response:

Defining response structures in ./api.js

JSON responses now have conformity. Each response has an ack field, with a top level status code of the response defined in the constants module. A timestamp field is also included, giving the app some context about when the response was sent. The response field holds a JSON object that can be passed into these structures, giving the flexibility to pass custom responses tailored for each route.

moment has been utilised to generate a unix timestamp, providing short, readable syntax compared to vanilla Javascript date objects.

To use these structures within routes themselves, simply import the module and embed them within the route in question:

var api = require('../api');router.post('/reset-password-request', async function (req, res, next) {  api.failure(res, {
reason: 'Deliberate failure'
})
});

The second argument containing the reason property will be injected into the response field of the api structure.

This approach maintains simple and readable syntax while adding more conformity to a project.

Although a few packages are available for random number generation, I have opted to use the seedrandom package for this use case. As the name suggests, seedrandom is a seeded random number generator for JavaScript.

We could simply call seedrandom() with no arguments, that will result in it being auto-seeded using the current time, DOM state, and other accumulated local entropy, per the documentation:

// generating a random code - no argumentsvar seedrandom = require('seedrandom');var prng = new seedrandom();
console.log(prng());

If you wanted to provide your own seed, the crypto module acts as a good resource to generate random byte strings, the size of which can be at your choosing:

// generating a random code - random `crypto` string as seedvar crypto = require('crypto')
var seedrandom = require('seedrandom');

const rng = seedrandom(
crypto.randomBytes(64).toString('base64'),
{ entropy: true }
);

randomBytes will generate cryptographically strong pseudo-random data, the amount of bytes of which can be provided, giving you more flexibility in the length of the seed phrase.

The output value of seedrandom() is a decimal between 0 and 1. To take 6 digits from this random number and present it as the password reset code, we can simply use Javascript’s substring() method:

// take 6 digits of random numberconst code = (rng()).toString().substring(3, 9);

For the very security conscious, you could also randomise which 6 digits you take from the code, using Math.random().

This is not a database focussed insight, but for completeness, this may be how you would update a users collection within a MongoDB database, adding the reset code to a user document:

// update mongo document with reset code

await db
.collection('users')
.updateOne({
email: email
}, {
$set: {
reset_password: {
ip: ip,
code: code,
expire_timestamp: moment().add(5, 'minutes').unix(),
created_timestamp: moment().unix(),
verified: false
}
}
});

We’re also defining a timestamp 5 minutes in the future as the time the code no longer becomes valid. 5 minutes gives the user enough time to receive the code via email, and attempt to verify it in-app.

Once your random code is generated, store it in your database to persist it. Later when the user attempts to verify the code, you’ll want to compare the sent code to the one you hold.

The last piece of this puzzle is to send the requester this randomly generated code to their email. To do so, I have chosen the mailgun-js API.

Mailgun provide a simple API for Nods JS that can be used once a domain has been verified in your dashboard. Firstly add it as a dependency to your project:

yarn add mailgun-js

Using a secret key provided by Mailgun for an authentication mechanism, we can simply trigger an email to be sent from an email address of your registered domain, to the user.

Your secret key is displayed in your Settings -> API Keys section of your dashboard.

Import the package into your routes file:

var mailgun = require("mailgun-js")

And send the email once the random code has been generated:

Once sent, you can go ahead and send a successful response object:

// response back to appapi.success(res, {
message: 'Check your email',
});In Summary

And with this in place, you have provided a complete implementation of the reset code generation.

In Summary

This piece has been an overview of both a password reset boilerplate project in React Native, as well as a walkthrough of a backend service to generate a random code, sent to the requester’s email address.

We’ve touched on major concepts involved in a password reset mechanism, that include:

  • Verifying the ownership of an account by providing account data
  • How to structure the React Native side, comprising of a multi-form setup to manage the reset process
  • How to trigger secure reset codes through Node JS or Express based API services

To read more about React Native authentication in general, check out my article on the subject, focussing on the concept of the authentication token used in conjunction with React Native APIs like AsyncStorage:

Programmer and Author. Director @ JKRBInvestments.com. Creator of ChineseGrammarReview.app for iOS.

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