In our serverless notes app we’ve used Cognito User Pool to sign up and login our users. In the frontend we’ve used AWS Amplify in our React app. However, if our users have forgotten their passwords, we need to have a way for them to reset their password. In this chapter we will look at how to do this.

The version of the notes app used in this chapter is hosted in a:

Let’s look at the main changes we need to make to allow users to reset their password.

Add a Reset Password Form

We are going to create a src/containers/ResetPassword.js.

import React, { useState } from "react";
import { Auth } from "aws-amplify";
import { Link } from "react-router-dom";
import {
  FormText,
  FormGroup,
  FormControl,
  FormLabel,
} from "react-bootstrap";
import { BsCheck } from "react-icons/bs";
import LoaderButton from "../components/LoaderButton";
import { useFormFields } from "../lib/hooksLib";
import { onError } from "../lib/errorLib";
import "./ResetPassword.css";

export default function ResetPassword() {
  const [fields, handleFieldChange] = useFormFields({
    code: "",
    email: "",
    password: "",
    confirmPassword: "",
  });
  const [codeSent, setCodeSent] = useState(false);
  const [confirmed, setConfirmed] = useState(false);
  const [isConfirming, setIsConfirming] = useState(false);
  const [isSendingCode, setIsSendingCode] = useState(false);

  function validateCodeForm() {
    return fields.email.length > 0;
  }

  function validateResetForm() {
    return (
      fields.code.length > 0 &&
      fields.password.length > 0 &&
      fields.password === fields.confirmPassword
    );
  }

  async function handleSendCodeClick(event) {
    event.preventDefault();

    setIsSendingCode(true);

    try {
      await Auth.forgotPassword(fields.email);
      setCodeSent(true);
    } catch (error) {
      onError(error);
      setIsSendingCode(false);
    }
  }

  async function handleConfirmClick(event) {
    event.preventDefault();

    setIsConfirming(true);

    try {
      await Auth.forgotPasswordSubmit(
        fields.email,
        fields.code,
        fields.password
      );
      setConfirmed(true);
    } catch (error) {
      onError(error);
      setIsConfirming(false);
    }
  }

  function renderRequestCodeForm() {
    return (
      <form onSubmit={handleSendCodeClick}>
        <FormGroup bsSize="large" controlId="email">
          <FormLabel>Email</FormLabel>
          <FormControl
            autoFocus
            type="email"
            value={fields.email}
            onChange={handleFieldChange}
          />
        </FormGroup>
        <LoaderButton
          block
          type="submit"
          bsSize="large"
          isLoading={isSendingCode}
          disabled={!validateCodeForm()}
        >
          Send Confirmation
        </LoaderButton>
      </form>
    );
  }

  function renderConfirmationForm() {
    return (
      <form onSubmit={handleConfirmClick}>
        <FormGroup bsSize="large" controlId="code">
          <FormLabel>Confirmation Code</FormLabel>
          <FormControl
            autoFocus
            type="tel"
            value={fields.code}
            onChange={handleFieldChange}
          />
          <FormText>
            Please check your email ({fields.email}) for the confirmation code.
          </FormText>
        </FormGroup>
        <hr />
        <FormGroup bsSize="large" controlId="password">
          <FormLabel>New Password</FormLabel>
          <FormControl
            type="password"
            value={fields.password}
            onChange={handleFieldChange}
          />
        </FormGroup>
        <FormGroup bsSize="large" controlId="confirmPassword">
          <FormLabel>Confirm Password</FormLabel>
          <FormControl
            type="password"
            value={fields.confirmPassword}
            onChange={handleFieldChange}
          />
        </FormGroup>
        <LoaderButton
          block
          type="submit"
          bsSize="large"
          isLoading={isConfirming}
          disabled={!validateResetForm()}
        >
          Confirm
        </LoaderButton>
      </form>
    );
  }

  function renderSuccessMessage() {
    return (
      <div className="success">
        <p><BsCheck size={16} /> Your password has been reset.</p>
        <p>
          <Link to="/login">
            Click here to login with your new credentials.
          </Link>
        </p>
      </div>
    );
  }

  return (
    <div className="ResetPassword">
      {!codeSent
        ? renderRequestCodeForm()
        : !confirmed
        ? renderConfirmationForm()
        : renderSuccessMessage()}
    </div>
  );
}

Let’s quickly go over the flow here:

  • We ask the user to put in the email address for their account in the renderRequestCodeForm().
  • Once the user submits this form, we start the process by calling Auth.forgotPassword(fields.email). Where Auth is a part of the AWS Amplify library.
  • This triggers Cognito to send a verification code to the specified email address.
  • Then we present a form where the user can input the code that Cognito sends them. This form is rendered in renderConfirmationForm(). And it also allows the user to put in their new password.
  • Once they submit this form with the code and their new password, we call Auth.forgotPasswordSubmit(fields.email, fields.code, fields.password). This resets the password for the account.
  • Finally, we show the user a sign telling them that their password has been successfully reset. We also link them to the login page where they can login using their new details.

Let’s also add a couple of styles.

Add the following to src/containers/ResetPassword.css.

@media all and (min-width: 480px) {
  .ResetPassword {
    padding: 60px 0;
  }

  .ResetPassword form {
    margin: 0 auto;
    max-width: 320px;
  }

  .ResetPassword .success {
    max-width: 400px;
  }
}

.ResetPassword .success {
  margin: 0 auto;
  text-align: center;
}
.ResetPassword .success .glyphicon {
  color: grey;
  font-size: 30px;
  margin-bottom: 30px;
}

Add the Route

Finally, let’s link this up with the rest of our app.

Add the route to src/Routes.js.

<Route
  path="/login/reset"
  element={
    <UnauthenticatedRoute>
      <ResetPassword />
    </UnauthenticatedRoute>
  }
/>

And import it in the header.

import ResetPassword from "./containers/ResetPassword";

Now we want to make sure that our users are directed to this page when they are trying to login.

So let’s add a link in our src/containers/Login.js. Add it above our login button.

<Link to="/login/reset">Forgot password?</Link>

And import the Link component in the header.

import { Link } from "react-router-dom";

And finally add some style to the link by adding the following to src/containers/Login.css

.Login form a {
  margin-bottom: 15px;
  display: block;
  font-size: 14px;
}

That’s it! We should now be able to navigate to /login/reset or go to it from the login page in case we need to reset our password.

Login page forgot password link screenshot

And from there they can put in their email to reset their password.

Forgot password page screenshot

Next, let’s look at how our logged in users can change their password.