In this tutorial, you will learn how to implement Forget Password functionality in your SpringBoot application.

The Password Reset Flow
- From the login page, the user clicks on the ‘Forgot Password’ link.
- The request hits the /forgotPassword route in the SecurityController
- The forgotPassword.html template (which is in the security directory) is returned to the user. This template has a form with a single email field.
- User enters his email and submits the form.
- This new request arrives via the /passwordRequest route in the PasswordResetController
- The forgottenPassword(email) is invoked with the passed in user’s email
- If and email exists, then a token is generated and send along to the user via a password reset email
- User receives the email and clicks on the link in the email
- The request arrives via the /passwordChange route in the PasswordResetController
- The changePassword() method sends the user the password reset form along with the token. This form contains two fields: newPassword and confirmPassword
- User fills the form and submits via the /passwordChange route
- The updatePassword is called with the password and token and the user data is saved
- The user is redirected to the passwordChangeSuccessful page
Showing the Forgot Password Form
Step 1 – Setup the /forgotPassword route
This is the first endpoint that would be hit when a user clicks on the Forgot Password link in the login form where the user must enter his email or username.
- Add the forgotPassword() method to the SecurityController
@GetMapping("/forgotPassword") public String forgotPassword(){ return "security/forgotPassword"; }
2. Create the forgotPassword.html template in the security folder. This is the template that displays a form for use to enter his email. Get it here.
3. Update the AppSecurityConfig to allow the /forgotPassword route
4. Test(1) the /forgotPassword route
Sending the ForgetPassword Email
Step 2 – Create the ForgotPasswordEmailContext
This would be similar to the AccountVerificationEmail context but for password reset request. It will extend AbstractEmailContext.
The difference would be that it would use the forgot-password template from the mailing folder. Do the following:
- add the forgotPassword.html template to the security folder. Get it from here.
- copy and modify the AccountVerificationEmailContext. The different could be in the verification url path and the name of course.
Step 3 – Setup the /passwordRequest Route and sendPasswordResetEmail Function
When the user enters his email in the forgotPassword.html form and submits, the request comes to this route. Do the following:
- Create and Implement the UserAccountService for managing password reset request. It would have two functions
- forgottenPassword(username) for accepting the request containing the user email
- updatePassword(password, token) for performing password update. Takes the new password and performs the update
- Write the sendResetPasswordEmail(user) function. This function takes the user as input and does the following:
- create a secureToken
- sets the user and saves the token
- creates a new ForgottenPasswordEmailContext
- sets the user and the token
- sends the email
The sendResetPasswordResetEmail() is given below:
protected void sendResetPasswordEmail(User user) { SecureToken secureToken= secureTokenService.createSecureToken(); secureToken.setUser(user); secureTokenRepository.save(secureToken); ForgotPasswordEmailContext emailContext = new ForgotPasswordEmailContext(); emailContext.init(user); emailContext.setToken(secureToken.getToken()); emailContext.buildVerificationUrl(baseURL, secureToken.getToken()); try { emailService.sendMail(emailContext); } catch (MessagingException e) { e.printStackTrace(); } }
3. Override the forgottenPassword(user) function. It simply finds the user and sends the email
4. Add the forgot-password html template to the mailing folder. Get it from here.
5. Implement the /passwordRequest controller method. It’s named resetPassword. Here’s the implementation here:
@PostMapping("/passwordRequest") // Display form with only email field public String resetPassword(final ResetPasswordData forgotPasswordForm, RedirectAttributes redirAttr) { try { customerAccountService.forgottenPassword(forgotPasswordForm.getEmail()); // Send email } catch (UnkownIdentifierException e) { // log the error redirAttr.addFlashAttribute("error", messageSource.getMessage("error", null, LocaleContextHolder.getLocale()) ); } redirAttr.addFlashAttribute("message", messageSource.getMessage("user.forgotpwd.msg", null, LocaleContextHolder.getLocale()) ); return REDIRECT_LOGIN; }
6. Test(2) that email is raised by the /passwordRequest
Displaying the ChangePassword Form by Clicking the Link
Step 4 – Create the PasswordResetData and PasswordResetController
1. Create the PasswordResetData. This is the POJO for data used for password reset and would have 4 string fields:
- token
- password
- repeatPassword
2. Create the PasswordResetController and write the controller method /passwordRequest. This method is named resetPassword and is given below:
@PostMapping("/passwordRequest") public String resetPassword(final ResetPasswordData forgotPasswordForm, RedirectAttributes redirAttr) { try { customerAccountService.forgottenPassword(forgotPasswordForm.getEmail()); // Send email } catch (UnkownIdentifierException e) { // log the error redirAttr.addFlashAttribute("error", messageSource.getMessage("error", null, LocaleContextHolder.getLocale()) ); } redirAttr.addFlashAttribute("message", messageSource.getMessage("user.forgotpwd.msg", null, LocaleContextHolder.getLocale()) ); return REDIRECT_LOGIN; }
3. Update the AppSecurityConfig to allow /passwordRequest route
4. Create the changePassword template in the security folder. Get it here
Step 5 – Setup the /passwordChange route for GET
This is the route that will be hit when the user clicks on the link in the email. The token is received and if it’s not empty, we create a ResetPasswordData form, sets the token and sends it to the user via the changePassword template.
1. Write the controller method for passwordChange. The method is given below:
@GetMapping("/passwordChange") public String changePassword(@RequestParam(required = false) String token, final RedirectAttributes redirAttr, final Model model) { if (StringUtils.isEmpty(token)) { redirAttr.addFlashAttribute("tokenError", messageSource.getMessage("user.registration.verification.missing.token", null, LocaleContextHolder.getLocale()) ); return REDIRECT_LOGIN; } ResetPasswordData data = new ResetPasswordData(); data.setToken(token); setResetPasswordForm(model, data); // add the resetPassword form to the model to send to the template return "security/changePassword"; }
You will have an error at the setPasswordResetForm(model, data)
2. Create the setPasswordResetForm() function – it adds the passwordResetData to the model. The key is “forgotPassword”.
3. Update the AppSecurityConfig to allow /passwordChange route
4. Test(3) that the link in the email displays the password request form
Performing the Password Update (Fill and Submit new Password)
Step 7 – Setup the /passwordChange Route for Post
Here, the user enters and confirms the new password and clicks on submit.
- Override the updatePassword() method in the UserAccountService. You can uncomment the code and implement the method. This is given below:
@Override public void updatePassword(String password, String token) throws InvalidTokenException, UnknownIdentifierException { SecureToken secureToken = secureTokenService.findByToken(token); if (Objects.isNull(secureToken) | !StringUtils.equals(token, secureToken.getToken()) | secureToken.isExpired()){ throw new InvalidTokenException("Token does not exist"); } User user = userRepository.getById(secureToken.getUser().getId()); if(Objects.isNull(user)){ throw new UnknownIdentifierException("User does not exist for this user"); } user.setPassword(cryptPasswordEncoder.encode(password)); userRepository.save(user); secureTokenService.removeToken(secureToken); }
2. Write the changePassword() controller method. This would call the updatePassword method from the service.
@PostMapping("/passwordChange") public String changePassword(final ResetPasswordData data, Model model) { try { userAccountService.updatePassword(data.getPassword(), data.getToken()); } catch (InvalidTokenException | UnknownIdentifierException e) { e.printStackTrace(); model.addAttribute("tokenError", messageSource.getMessage("user.registration.verification.invalid.token", null, LocaleContextHolder.getLocale())); return "security/changePassword"; } model.addAttribute("passwordUpdatedMsg", messageSource.getMessage("user.password.updated.msg", null, LocaleContextHolder.getLocale())); setResetPasswordForm(model, new ResetPasswordData()); return "security/passwordChangeSuccessful"; }
3. Create the passwordChangeSuccessful.html template in the security folder. Get it here.
4. Add the user.password.updated.msg to your messages.properties file
5. Test(4) that the password is updated after a new password is entered and submitted.
Happy coding!!