Forgot password is basic functionality which all web/mobile applications has it. In this article we are going to show how to implement this in Node.js express framework. One required thing to implement this is the user should have email address setup when signing up or in your user profile, so email link for forgotten password can be sent.


Step 1

The user is taken to the reset password page where he is required to enter his email address. Once the email address is entered,a request is sent to the backend (node.js) to validate if this email address exists in any user profile.

For this to achieve we create a controller file say user.js which processes the reset-password request

user.js
JavaScript
1userRoutes.post('/reset-password', function (req, res) {
2 const email = req.body.email
3 User
4 .findOne({
5 where: { email: email },//checking if the email address sent by client is present in the db(valid)
6 })
7 .then(function (user) {
8 if (!user) {
9 return throwFailed(res, 'No user found with that email address.')
10 }
11 ResetPassword
12 .findOne({
13 where: { userId: user.id, status: 0 },
14 }).then(function (resetPassword) {
15 if (resetPassword)
16 resetPassword.destroy({
17 where: {
18 id: resetPassword.id
19 }
20 })
21 token = crypto.randomBytes(32).toString('hex')//creating the token to be sent to the forgot password form (react)
22 bcrypt.hash(token, null, null, function (err, hash) {//hashing the password to store in the db node.js
23 ResetPassword.create({
24 userId: user.id,
25 resetPasswordToken: hash,
26 expire: moment.utc().add(config.tokenExpiry, 'seconds'),
27 }).then(function (item) {
28 if (!item)
29 return throwFailed(res, 'Oops problem in creating new password record')
30 let mailOptions = {
31 from: '"<jyothi pitta>" jyothi.pitta@ktree.us',
32 to: user.email,
33 subject: 'Reset your account password',
34 html: '<h4><b>Reset Password</b></h4>' +
35 '<p>To reset your password, complete this form:</p>' +
36 '<a href=' + config.clientUrl + 'reset/' + user.id + '/' + token + '">' + config.clientUrl + 'reset/' + user.id + '/' + token + '</a>' +
37 '<br><br>' +
38 '<p>--Team</p>'
39 }
40 let mailSent = sendMail(mailOptions)//sending mail to the user where he can reset password.User id and the token generated are sent as params in a link
41 if (mailSent) {
42 return res.json({ success: true, message: 'Check your mail to reset your password.' })
43 } else {
44 return throwFailed(error, 'Unable to send email.');
45 }
46 })
47 })
48 });
49 })
50})
 
Understanding user.js

The email sent from the forgot password page is checked if it is present in the database.

If the email address doesn’t match, a response would be sent stating 'No user found with that email address.'

JavaScript
1userRoutes.post('/reset-password', function (req, res) {
2 const email = req.body.email
3 User.findOne({ email: email })
4 .exec()
5 .then(function (user) {
6 if (!user) {
7 return throwFailed(res, 'No user found with that email address.')
8 }

In the below code

  • We will create entry in ‘forgotpassword’ table, if the email ID is present in the users table.
  • The code also checks and delete if any previous forgot_password entries for this particular user id.
  • The new record created in the database consists of the new hashed token and the token generated before hashing is sent in the forgot-password form.
JavaScript
1token = crypto.randomBytes(32).toString('hex')//this token would be sent in the link to reset forgot password
2bcrypt.hash(token, null, null, function (err, hash) {
3 let password = new ResetPassword({
4 userId: user._id,
5 resetPasswordToken: hash,
6 expire: moment.utc().add(config.tokenExpiry, 'seconds'),
7 });
8 password.save(function (err) {
9 if (err) throw err;
10 });
11});

Here using the below code an email with a link consisting of a token,user id and expiry time is sent to the user.

JavaScript
1let mailOptions = {
2 from: config.senderEmail,
3 to: user.email,
4 subject: 'Reset your account password',
5 html: '<h4><b>Reset Password</b></h4>' +
6 '<p>To reset your password, complete this form:</p>' +
7 '<a href=' + config.clientUrl + 'reset/' + user._id + '/' + token + '">' + config.clientUrl + 'reset/' + user._id + '/' + token + '</a>' +
8 '<br><br>' +
9 '<p>--Team</p>'
10};
11// send mail with defined transport object
12transporter.sendMail(mailOptions, (error, info))

Step 2

In this step we will code the part where the user can provide new password. As described in step 1 The link generated for the forgot password uses ‘guestroute’, in this route we will call component ‘NewPasswordContainer’.

JavaScript
1import NewPasswordContainer from '../modules/resetPassword/components/new-password-container'
HTML / XML
1<GuestRoute exact path="/reset/:userId/:token" component={NewPasswordContainer} />
NewPasswordContainer.js
JavaScript
1handleValidation(){//validating password and confirm password
2 let password = this.state.password
3 let confirmPassword = this.state.confirmPassword
4 let errors = {};
5 let formIsValid = true;
6 //validation
7 if (!password) {
8 formIsValid = false;
9 errors["password"] = "Password is required.";
10 }
11 if (!confirmPassword) {
12 formIsValid = false;
13 errors["confirmPassword"] = "Confirmation password is required.";
14 }
15 if (password && confirmPassword) {
16 if (password.length < 5) {
17 formIsValid = false;
18 errors["password"] = "Password minimum lenght is 5.";
19 } else if (password !== confirmPassword) {
20 formIsValid = false;
21 errors["password"] = "The password confirmation does not match.";
22 }
23 }
24 this.setState({ errors: errors })
25 return formIsValid;
26}

/*On submitting the form we call the onStoreNewPassword method*/

JavaScript
1import * as ResetPasswordAction from '../actions/reset-password-actions'
2async onStoreNewPassword(event) {
3event.preventDefault();
4let userId = this.props.match.params.userId
5let resetToken = this.props.match.params.token
6if (this.handleValidation()) {
7let newPasswordBtnRef = Object.assign({}, this.state.newPasswordBtnRef)
8newPasswordBtnRef.current.disabled = true
9newPasswordBtnRef.current.innerHTML = "Loading..."
10this.setState({ newPasswordBtnRef })
11var response = await ResetPasswordAction.storeNewPassword(this.state.password, userId, resetToken)//calling an action to send data to the node.js store-password api
12if (response) {
13newPasswordBtnRef.current.disabled = false
14newPasswordBtnRef.current.innerHTML = "Reset Password"
15this.setState({ newPasswordBtnRef })
16appHistory.push('/login')
17}
18}
19}
reset-password-actions.js
JavaScript
1/*Store-password function */
2export function storeNewPassword(password, userId, token) {
3 store.dispatch(showLoading())
4 return request.post('store-password', {//node.js api to send the new password to
5 userId: userId,
6 password: password,
7 token: token
8 }).then(response => {
9 store.dispatch(hideLoading())
10 if (response.data.success) {
11 notification.success(response.data.message)
12 return true;
13 } else {
14 notification.error(response.data.message)
15 }
16 }).catch(error => {
17 console.log(error)
18 })
19}
Decoding NewPasswordContainer.js line by line

The NewPasswordContainer.js file validates the password and the confirm password

JavaScript
1handleValidation(){//validating password and confirm password
2 let password = this.state.password

Once the user clicks on submit we invoke the onStoreNewPassword which takes the user id and token to the node.js to update the password using an action

JavaScript
1async onStoreNewPassword(event) {
2 event.preventDefault();
3 let userId = this.props.match.params.userId
4 let resetToken = this.props.match.params.token
5 if (this.handleValidation()) {
JavaScript
1var response = await ResetPasswordAction.storeNewPassword(this.state.password, userId, resetToken)//action called

The storeNewPassword function sends the token and the user id to update the password in node.js

Plain text
1 return request.post('store-password', {//node.js api to send the new password to

Step 3

On clicking submit in the forgot password page the hashed token in the db is verified against the one in the forgot password form to validate if the token is still valid and hasn't expired. If both tokens match a new password would be updated in the users record by encrypting the password.

JavaScript
1userRoutes.post('/store-password', function (req, res) {//handles the new password from react
2 const userId = req.body.userId
3 const token = req.body.token
4 const password = req.body.password
5 ResetPassword.findOne({ where: { userId: userId, status: 0 } })
6 .then(function (resetPassword) {
7 if (!resetPassword) {
8 return throwFailed(res, 'Invalid or expired reset token.')
9 }
10 bcrypt.compare(token, resetPassword.token, function (errBcrypt, resBcrypt) {// the token and the hashed token in the db are verified befor updating the password
11 let expireTime = moment.utc(resetPassword.expire)
12 let currentTime = new Date();
13 bcrypt.hash(password, null, null, function (err, hash) {
14 User.update({
15 password: hash,
16 },
17 { where: { id: userId } }
18 ).
19 then(() => {
20 ResetPassword.update({
21 status: 1
22 }, { where: { id: resetPassword.id } }).
23 then((msg) => {
24 if (!msg)
25 throw err
26 else
27 res.json({ success: true, message: 'Password Updated successfully.' })
28 })
29 })
30 });
31 });
32 }).catch(error => throwFailed(error, ''))
33})

Step 4

Once the new password is updated successfully, the user is redirected to the login page where he can use his new password for login.

onStoreNewPassword function in step2.

Plain text
1if (response) {
2 newPasswordBtnRef.current.disabled = false
3 newPasswordBtnRef.current.innerHTML = "Reset Password"
4 this.setState({ newPasswordBtnRef })
5 appHistory.push('/login') //navigating the login page
6}