Our Work

Implement Reset Password functionality in Node.js/ Express

Updated 1 month and 15 days ago

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.

Code of this tutorial is provided in this Git Repository

******

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
userRoutes.post('/reset-password', function (req, res) {
   const email = req.body.email
   User
       .findOne({
           where: {email: email},//checking if the email address sent by client is present in the db(valid)
       })
       .then(function (user) {
           if (!user) {
               return throwFailed(res, 'No user found with that email address.')
           }
           ResetPassword
               .findOne({
                   where: {userId: user.id, status: 0},
               }).then(function (resetPassword) {
               if (resetPassword)
                   resetPassword.destroy({
                       where: {
                           id: resetPassword.id
                       }
                   })
               token = crypto.randomBytes(32).toString('hex')//creating the token to be sent to the forgot password form (react)
               bcrypt.hash(token, null, null, function (err, hash) {//hashing the password to store in the db node.js
                   ResetPassword.create({
                       userId: user.id,
                       resetPasswordToken: hash,
                       expire: moment.utc().add(config.tokenExpiry, 'seconds'),
                   }).then(function (item) {
                       if (!item)
                           return throwFailed(res, 'Oops problem in creating new password record')

                       let mailOptions = {
                           from: '"<jyothi pitta>" [email protected]',
                           to: user.email,
                           subject: 'Reset your account password',
                           html: '<h4><b>Reset Password</b></h4>' +
                           '<p>To reset your password, complete this form:</p>' +
                           '<a href=' + config.clientUrl + 'reset/' + user.id + '/' + token + '">' + config.clientUrl + 'reset/' + user.id + '/' + token + '</a>' +
                           '<br><br>' +
                           '<p>--Team</p>'
                       }

                       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

                       if (mailSent) {
                           return res.json({success: true, message: 'Check your mail to reset your password.'})
                       } else {
                           return throwFailed(error, 'Unable to send email.');
                       }

                   })
               })

           });
       })

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

userRoutes.post('/reset-password', function (req, res) {
const email = req.body.email
User.findOne({ email: email })
.exec()
.then(function (user) {
if (!user) {
return throwFailed(res, 'No user found with that email address.')
}

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.

 token = crypto.randomBytes(32).toString('hex')//this token would be sent in the link to reset forgot password
bcrypt.hash(token, null, null, function (err, hash) {
let password = new ResetPassword({
userId: user._id,
resetPasswordToken: hash,
expire: moment.utc().add(config.tokenExpiry, 'seconds'),
});
password.save(function (err) {
if (err) throw err;
});
});

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

let mailOptions = {
from: config.senderEmail,
to: user.email,
subject: 'Reset your account password',
html: '<h4><b>Reset Password</b></h4>' +
'<p>To reset your password, complete this form:</p>' +
'<a href=' + config.clientUrl + 'reset/' + user._id + '/' + token + '">' + config.clientUrl + 'reset/' + user._id + '/' + token + '</a>' +
'<br><br>' +
'<p>--Team</p>'
};
// send mail with defined transport object
transporter.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’.

import NewPasswordContainer from '../modules/resetPassword/components/new-password-container'
<GuestRoute exact path="/reset/:userId/:token" component={NewPasswordContainer} />
NewPasswordContainer.js
handleValidation(){//validating password and confirm password
let password = this.state.password
let confirmPassword = this.state.confirmPassword
let errors = {};
let formIsValid = true;
//validation
if(!password) {
formIsValid = false;
errors["password"] = "Password is required.";
}
if (!confirmPassword) {
formIsValid = false;
errors["confirmPassword"] = "Confirmation password is required.";
}
if (password && confirmPassword) {
if (password.length < 5) {
formIsValid = false;
errors["password"] = "Password minimum lenght is 5.";
} else if (password !== confirmPassword) {
formIsValid = false;
errors["password"] = "The password confirmation does not match.";
}
}
this.setState({errors: errors})
return formIsValid;
}

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

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

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

 handleValidation(){//validating password and confirm password
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

async onStoreNewPassword(event) {
event.preventDefault();
let userId = this.props.match.params.userId
let resetToken = this.props.match.params.token
if (this.handleValidation()) {
var 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

 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.

userRoutes.post('/store-password', function (req, res) {//handles the new password from react
const userId = req.body.userId
const token = req.body.token
const password = req.body.password
ResetPassword.findOne({ where:{ userId: userId ,status : 0 }})
.then(function (resetPassword) {
if (!resetPassword) {
return throwFailed(res, 'Invalid or expired reset token.')
}
bcrypt.compare(token, resetPassword.token, function (errBcrypt, resBcrypt) {// the token and the hashed token in the db are verified befor updating the password
let expireTime = moment.utc(resetPassword.expire)
let currentTime = new Date();
bcrypt.hash(password, null, null, function (err, hash) {
User.update({
password: hash,
},
{ where: { id: userId }}
).
then(() => {
ResetPassword.update({
status: 1
},{ where: {id : resetPassword.id}}).
then((msg) => {
if(!msg)
throw err
else
res.json({ success: true, message: 'Password Updated successfully.' })
})
})
});
});
}).catch(error => throwFailed(error, ''))
})

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

 if (response) {
newPasswordBtnRef.current.disabled = false
newPasswordBtnRef.current.innerHTML = "Reset Password"
this.setState({ newPasswordBtnRef })
appHistory.push('/login') //navigating the login page
}

 

Tags

    No tag results found for this post