Skip to content
Buy 10+ on select items — save 10% auto-applied
Free US shipping on orders $35+
Order by 3pm ET — ships same-day from the US
Skip to main content

React Native Node MongoDB: Forgot password flow | ShillehTek

October 23, 2023 164 views

React Native Node MongoDB: Forgot password flow | ShillehTek
Project

Build a Forgot Password flow with React Native, Node (Express), and MongoDB, including emailed reset tokens and secure password updates with ShillehTek.

30 min Intermediate

Video Tutorial (Optional)

Watch first if you want to follow the full Forgot Password implementation in real time for React Native, Node (Express), and MongoDB.

If you also want a deeper walkthrough on sending emails from Node, this related video is referenced in the build:

Project Overview

React Native + Node (Express) + MongoDB Forgot Password: you will build a complete password reset flow that lets a user request a reset token by email and then confirm a new password using that token.

This tutorial assumes you are already familiar with the MERN stack basics and have experience with React Native hooks and Express.

  • Time: 30 to 60 minutes
  • Skill level: Intermediate
  • What you will build: A Forgot Password flow with a React Native UI, Express endpoints, MongoDB token storage, and password update with bcrypt

Parts List

From ShillehTek

  • None for this software-only tutorial.

External

  • React Native app with navigation (uses navigation.navigate())
  • Node.js + Express server
  • MongoDB + Mongoose (SIGNIN schema/model)
  • Axios (frontend HTTP requests)
  • bcrypt (password hashing on the backend)
  • Email sending utility on the backend (referenced as sendEmail())

Note: The example requests post to http://localhost:3001. Adjust the base URL for device testing or production deployments.

Step-by-Step Guide

Step 1 - Add a Forgot Password button on the sign-in screen

Goal: Provide a clear entry point for users to start the reset flow.

What to do: Create a button that triggers an onPress handler that navigates to your Forgot Password screen and clears any existing auth form state.

Code:

<Button style={styles.forgotPasswordButton} onPress={handleForgotPass}>
  Forgot Password
</Button>

Code:

const handleForgotPass = () => {
    navigation.navigate('ForgotPassword')
    clearStates()
  }
React Native sign-in screen showing a Forgot Password button that navigates to the ForgotPassword route
Sign-in screen with a Forgot Password button that navigates to the next screen.

Expected result: Tapping the button takes the user to the Forgot Password screen.

Step 2 - Build the Forgot Password screen and request a reset token

Goal: Collect the user email, call the backend endpoint, and route to a confirmation screen if the request succeeds.

What to do: Add a Reset Password button in the frontend. In the handler, post the email to /resetPassword. On success, store the email in context and navigate to the confirmation screen.

Code:

<Button mode="contained" onPress={handleResetPassword} style={styles.button}>
  Reset Password
</Button>

Code:

  const { setForgotEmail } = useContext(AuthContext);
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');

  const handleResetPassword = async () => {
    try {
      const data = {
        email: email,
      };
      await axios.post('http://localhost:3001/resetPassword', data, {
        headers: {
          'Content-Type': 'application/json',
        },
      }).then((response) => {
        if (response.data.success) {
          setForgotEmail(email)
          navigation.navigate('ResetPasswordConfirmation');
        } else {
          setError('There was an issue resetting your password. Please try again.');
        }
      });
    } catch (error) {
      console.error(error);
    }
  };
React Native Forgot Password screen with email input and a Reset Password button
Forgot Password screen where the user submits their email.

Expected result: The app posts the email to the backend and navigates to the confirmation screen when the backend responds with success: true.

Step 3 - Implement the backend endpoint to generate and email a reset token

Goal: Verify the user exists, generate a reset token and expiration, save them to MongoDB, and email the token.

What to do: Create a POST /resetPassword endpoint that looks up the user by email, generates a token, stores resettoken and resettokenExpiration, and sends an email containing the token.

Code:

app.post('/resetPassword', async(req, res) => {
  try {
    const email = req.body.email;
    signinTable = Schemas.SIGNIN
    const existingUser = await signinTable.findOne({ email });

    if (!existingUser) {
      console.error({ success: false, message: 'There was an Error' });
      return res.send({ success: false, message: 'If user exists, an email was sent' });
    }

    const token = await generateCode(5)
    existingUser.resettoken = token;
    existingUser.resettokenExpiration = Date.now() + 3600000;
    await existingUser.save();
    await sendEmail(email, `Here is your Reset Token ${token}`)
    return res.send({ success: true, message: 'Email sent' });

  } catch (error) {
    console.error(error)
  }
})

Expected result: When the frontend calls /resetPassword, a token and expiration are saved to the user record and an email is sent with the token.

Step 4 - Build the reset confirmation screen and submit the new password

Goal: Let the user enter the verification code and a new password, then submit them to the backend to finalize the reset.

What to do: Add a Submit button and implement a handler that validates newPassword and confirmPassword match. Then post email, verificationCode, and password to /resetPasswordConfirm. On success, navigate to Home and clear the stored email.

Code:

<Button mode="contained" onPress={handleSubmit} style={styles.button}>
  Submit
</Button>

Code:

const handleSubmit = async () => {
    if (newPassword !== confirmPassword) {
      setErrorMessage('Passwords do not match. Please try again.');
      return;
    }

    try {
      const data = {
        email: forgotEmail,
        verificationCode: verificationCode,
        password: newPassword,
      };
      const response = await axios.post('http://localhost:3001/resetPasswordConfirm', data, {
        headers: {
          'Content-Type': 'application/json',
        },
      });
      if (response.data.success) {
        setSuccessMessage('Password reset successful!');
        setErrorMessage('')
        navigation.navigate('Home');
        setForgotEmail('')
      } else {
        setErrorMessage(response.data.message);
      }
    } catch (error) {
      console.error(error);
      setErrorMessage('An error occurred. Please try again later.');
    }
  };
React Native Reset Password Confirmation screen with verification code and new password fields and a Submit button
Confirmation screen where the user submits the token and new password.

Expected result: If the verification code and new password are accepted, the user navigates to the Home screen.

Step 5 - Implement the backend confirmation endpoint and update the password

Goal: Validate the verification code and expiration, then hash and save the new password.

What to do: Create a POST /resetPasswordConfirm endpoint that checks the user exists, compares the provided token, checks expiration, validates password strength (optional in this tutorial), then hashes the password with bcrypt and saves it to MongoDB.

Code:

app.post('/resetPasswordConfirm', async (req, res) => {
  try {
    signinTable = Schemas.SIGNIN
    const email = req.body.email
    const verificationCode = req.body.verificationCode
    const password = req.body.password
    const user = await signinTable.findOne({ email });
    passwordStrength = isStrongPassword(password)
    if (!passwordStrength.strong) {
      return res.send({ success: false, message: passwordStrength.missingRequirements.join('\n') });
    }
    if (!user || user.resettoken !== verificationCode) {
      return res.status(400).send({ success: false });
    }
    if (user.resettokenExpiration < new Date()) {
      return res.status(400).send({ success: false, message: 'Token has expired.' });
    }

    const hashedPassword = await bcrypt.hash(password, 10);
    user.password = hashedPassword;
    user.token = '';
    user.tokenExpiration = null;
    await user.save();
    return res.status(200).send({ success: true });
    
  } catch (error) {
    console.error(error);
    return res.status(500).send({ success: false, message: 'An error occurred. Please try again later.' });
  }
});

Expected result: The backend rejects invalid/expired tokens and stores the new bcrypt-hashed password for valid requests.

Step 6 - Add the MongoDB schema fields used by the reset flow

Goal: Persist the reset token and expiration in MongoDB.

What to do: Ensure your Mongoose schema includes the fields used in the endpoints, including resettoken and resettokenExpiration.

Code:

const signin = new Schema({
    password: {type:String, required:true},
    email: {type:String, required:true},
    code: {type:String, required:true},
    verified: {type:Boolean, required:true},
    resettoken: {type:String, required:false},
    resettokenExpiration: {type:Date, required:false}
})

const SIGNIN = mongoose.model('SIGNIN', signin,)
const mySchemas = {
    'SIGNIN': SIGNIN,
}

Expected result: Your user documents can store a reset token and expiration so the confirmation endpoint can validate them.

Conclusion

You built a complete Forgot Password flow using a React Native frontend, a Node/Express backend, and a MongoDB database. The user requests a reset token by email, then confirms the token and sets a new password that is hashed with bcrypt and saved back to MongoDB.

Want to support ShillehTek? Grab parts and tools from ShillehTek.com. If you want help customizing this flow for production, improving security, or integrating it into your product, check out our consulting services.