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()
}
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);
}
};
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.');
}
};
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.


