This article is a continuation of Part 2 - User Authentication in React. We recommended completing all parts in order.

Password Recovery != Showing Passwords

Before we get started, it’s important to understand that password recovery does not mean showing the user what their password is. As a rule, all passwords should be stored in your database using a 1-way hashing algorithm combined with a salt to prevent you or anyone, for that matter, being able to see or convert them back into plain text. To learn more about salted hashes please check out this article.

Password Recovery == Salted Hash w/ Password Reset

In this part of our series we will demonstrate how to securely build password recovery using password reset form with a simple question / answer to verify the user’s identity. As a best practice, the answer to the security question will be verified using a salted hash.

Note: Normally building salted hashes would require a server-side language such as ASP.net, nodeJS or PHP, however in this example the construction of the hash and the verification of it will be performed using the free MeshyDB API. No server-side language or experience is required for this tutorial.

TL;DR

Checkout the complete solution via GitHub. Don't forget though, you need a MeshyDB account to make this project run–don't panic, it's free!

Creating the Component

We will begin by first creating a new component that will house the password reset form. For this example, we will name this component src/PasswordRecoveryForm.js. If the code looks a little light, don’t worry, we’re going to come back to this page later and fill in all the missing parts.

src/PasswordRecoveryForm.js (new)
import React from 'react'  
import { MeshyClient } from "@meshydb/sdk";  
import Navigation from './Navigation'; 

class ForgotPassword extends React.Component 
{ 
    constructor(props) 
    { 
        super(props);  

        this.state = 
        { 
            success: null, 
            message: '' ,
            username: '',
            expires: '',
            hint: '',
            hash:'',
            newPassword: '',
            verificationCode: '',
            done: false,
        }; 

        this.handleChange = this.handleChange.bind(this);
        this.handleForgotPasswordSubmit = this.handleForgotPasswordSubmit.bind(this);
        this.handleResetPasswordSubmit = this.handleResetPasswordSubmit.bind(this);
    }  

    handleChange(event)  
    { 
        const target = event.target; 
        const value = target.type === 'checkbox' ? target.checked : target.value; 
        const name = target.name; 

        this.setState({ [name]: value }); 

        return true; 
    } 

    handleForgotPasswordSubmit(event)
    { 
    }

    handleResetPasswordSubmit(event)
    {
    }

    render() 
    {  
        return (  
            <div> 
                <Navigation />
                <div className="row"> 
                    <div className="col-lg-8 offset-lg-2">  
                        {this.state.success === false && 
                        <p className="alert alert-danger" role="alert"> 
                            {this.state.message} 
                        </p>}
                        <div> 
                            <h2 className="text-center">Password Reset</h2>
                            {this.state.hash === '' &&
                            <form onSubmit={this.handleForgotPasswordSubmit}>
                            </form>}
                            {this.state.hash !== '' &&
                            <form onSubmit={this.handleResetPasswordSubmit}> 
                            </form>}
                        </div>
                    </div>  
                </div> 
            </div>
        );  
    }  
 }  

export default ForgotPassword;

Routing

Now that we have a password recovery form, we need to add this component to our app by adding an import statement to src/App.js.

src/App.js
import ForgotPassword from './ForgotPassword';

The next step is to add a login route. If the router detects “/forgotpassword” in the url path it will render the <PasswordRecoveryForm> component.

src/App.js
<Route path="/forgotpassword">  
    <ForgotPassword />
</Route>

Navigation

Since password recovery is something the user is going to do when attempting to login, we need to add a link to the login form that routes the user to our new “/forgotpassword” route.

Internal links in React are achieved via the Link component that is part of "react-router-dom". Let's add this component to our existing import statement.

src/LoginForm.js
import { Redirect, Link } from "react-router-dom"; 

Now that we have our <Link> component imported, we can add an internal link directly below the sign in form that takes the user to our new page.

src/LoginForm.js
<p>
    <Link to="/forgotpassword">forgot password?</Link>
</p>

If you’ve done everything right your app should look something like this:

Identifying the User

Before we can reset the user’s password, we first need to know who it is we are dealing with. The simplest way to figure this out is to ask the user what their username is.

src/PasswordRecoveryForm.js
{this.state.hash === '' &&
<form onSubmit={this.handleForgotPasswordSubmit}> 
    <div className="form-group"> 
        <input type="text" className="form-control" placeholder="Enter your username" name="username" required onChange={this.handleChange} />
    </div> 
    <div className="form-group"> 
        <button type="submit" className="btn btn-primary btn-block">Submit</button> 
    </div>        
</form>}

With the form in place, the next thing to do is build out the submit event handler. The MeshyDB SDK has a function called forgotPassword(username). If the username provided exists, the API will return a password recovery model. We can then store the model in state to be used later.

src/PasswordRecoveryForm.js
handleForgotPasswordSubmit(event)
{
    MeshyClient.forgotPassword(this.state.username).then((x) => { 
        this.setState({ "success": true }); 
        this.setState({ "expires": x.expires});
        this.setState({ "hint": x.hint});
        this.setState({ "hash": x.hash});
    }).catch(e=>{ 
        this.setState({ "message" : e.response.message }); 
        this.setState({ "success" : false }); 
    }); 
            
    event.preventDefault(); 
}

If you’ve done everything right you should have a form that returns a JSON model similar to the following:

Understanding the Password Recovery Model

{ 
    "username": "jdoe", 
    "attempt": 1, 
    "hash": "...", 
    “expires": "1900-01-01T00:00:00.000Z", 
    “hint": "What was your best friend's name?" 
} 
                            

Hint

As mentioned earlier, MeshyDB supports multiple forms of password recovery, one of which being security question / answer. In this example, since we are using security question, we can assume the hint included in the response is a question intended for the user to answer in order to verify the user’s identity.

Hash

Included in this model is the hash that will verify if the user provided the right answer to the hint. The hash is salted in a way where it will be different each time the user attempts a password recovery.

Expires

Another important aspect to this model is the expires value. In this example the hash will expire 30 minutes after the user initiated the password reset attempt. This limit is intended to protect the user by not allowing time for the hash to be brute forced. If the user attempts to use the hash after it expires, they will be presented with a friendly error message.

Attempt

Finally, the property attempt indicates how many times this session a user has attempted password reset. You can increment this number each time you call forgotPassword(username, attempt) to rotate through all the available security questions (aka hint) the user has defined.

Resetting Password Using Verification Code

For a user to change their password they will need to provide an answer to the hint in the form of a verificationCode that, when combined with other information in the model along with the salt, will allow the MeshyDB server to produce an identical hash value.

Here is an example of what a reset password request might look like assuming the correct answer to "what was your best friend's name?" is "jill":

POST https://api.meshydb.com/{accountName}/users/resetpassword HTTP/1.1
Content-Type: application/json

{
    "username": "jdoe",
    "attempt": 1,
    "hash": "...",
    “expires": "1900-01-01T00:00:00.000Z",
    “hint": "What was your best friend's name?",
    “newPassword": "Pass123!",
    “verificationCode": "jill"
}

Note: verificationCodes are not case sensitive.

Now that we understand what data we need to reset the user’s password, let’s add the two text boxes for the user to collect the two missing pieces of information. The first text box will represent the verificationCode. We will use the hint property to display the question in the form of placeholder text. The second textbox will be where the user enters their newPassword. This textbox will be type password so that the user’s input is masked with (*).

Note: in this example we are not imposing any sort of constraints on the new password. If any value is provided the password is considered valid. You may want to add your own validation at this point, such as minimum length and capitalization or symbol requirements.

src/PasswordRecoveryForm.js
{this.state.hash !== '' &&
<form onSubmit={this.handleResetPasswordSubmit}> 
    <div className="form-group"> 
        <input type="text" className="form-control" placeholder={this.state.hint} name="verificationCode" required onChange={this.handleChange} />
    </div> 
    <div className="form-group"> 
        <input type="password" className="form-control" placeholder="New password" name="newPassword" required onChange={this.handleChange} />
    </div> 
    <div className="form-group"> 
        <button type="submit" className="btn btn-primary btn-block">Submit</button> 
    </div>         
</form>}

With the form in place, the next thing to do is build out the submit event handler. The MeshyDB SDK has a function called resetPassword(). If the verificationCode provided is correct, the API will return a success response code. Otherwise it will return a detailed error message that will instruct the user on what is wrong. This error message is designed to be user friendly so we can safely display that text using the message state variable directly on the form.

src/PasswordRecoveryForm.js
handleResetPasswordSubmit(event)
{
    MeshyClient.resetPassword(this.state).then(_ => { 
        this.setState({ "success": true }); 
        this.setState({ "done": true }); 
    }).catch(e=>{ 
        this.setState({ "message" : e.response.message }); 
        this.setState({ "success" : false }); 
    }); 
            
    event.preventDefault(); 
}

Login in After a Reset

Upon successful reset of the user’s password, we will want to redirect the user back to the login form with a message indicating their password reset worked. Let's start by importing the necessary <Redirect /> component.

src/PasswordRecoveryForm.js
import { Redirect } from "react-router-dom";

Now we can add a conditional <Redirect> with a target of "/loginform?reset=true" if the resetPassword() call succeeded. We can know the call succeeded using the state variable done and checking to see if it equals true.

src/PasswordRecoveryForm.js
{this.state.done === true &&
<Redirect to="/login?reset=true" />}

Accessing the Query String

The redirect path contained a special querystring parameter called reset. We need to read this value and if it is true, we will want to show a message to the user indicating their password reset was successful.

To do this we first need to add a new node package. You can open your terminal and run the following command to install the required package:

npm install query-string

Now that the new node package is installed, we can add the necessary import statement to src/Login.js.

src/LoginForm.js
import queryString from 'query-string';

Exporting Components With Router

By default, the query string is not something a component has access to, however you can give your component access by wrapping the component name in the export statement with the function withRouter([component]). This will add the location property to the props parameter already included in your constructor.

Let's first import the necessary function by updating our existing import statement from 'react-router-dom'.

src/LoginForm.js
import { Redirect, Link, withRouter } from "react-router-dom";

Now let's change our existing export statement to include the withRouter() modification.

src/LoginForm.js
export default withRouter(LoginForm);

Displaying the Success Message

With those couple changes in place we can now read the query string and conditionally set the message state variable if reset === true.

src/LoginForm.js
//constructor
const query = this.props.location ? queryString.parse(this.props.location.search) : {};

this.state = 
{ 
    success: null, 
    message: query.reset == "true" ? 'Your password has been reset. Please login.' : ''
};

Currently the login form will only render the message state variable to the UI if the success state variable is false (it also shows red span indicating an error). Let’s add a more friendly span (right above the existing error span) to show success message.

src/LoginForm.js
{(this.state.success === null && this.state.message) && 
<p className="alert alert-success" role="alert"> 
    {this.state.message} 
</p>}

Putting it All Together

There you have it, you users can now securely reset their password.

We can verify our app works by recovering our password with the user we created from our previous article Part 1 - User Registration in React.

What Next?

This blog is the third part of a multi-part series where we will be demonstrating how to create a complete app using React. To continue in the series please click the link below. We suggest subscribing to our blog and following us on twitter for weekly updates.

Part 4 - Creating a Data Entry Form in React