1. Overview

In this tutorial – we’re continuing the ongoing Registration with Spring Security series with a look at the basic “I forgot my password” feature – so that the user can safely reset their own password when they need to.

2. Request the Reset of Your Password

A password reset flow typically starts when the user clicks some kind of “reset” button on the Login page. Then, we can ask the user for their email address or other identifying information. Once confirmed, we can generate a token and send an email to the user.

The following diagram visualizes the flow that we'll implement in this article:

Request password reset e-mail

3. The Password Reset Token

Let’s start by creating a PasswordResetToken entity to use it for resetting the user’s password:

@Entity
public class PasswordResetToken {
 
    private static final int EXPIRATION = 60 * 24;
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
 
    private String token;
 
    @OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
    @JoinColumn(nullable = false, name = "user_id")
    private User user;
 
    private Date expiryDate;
}

When a password reset is triggered – a token will be created and a special link containing this token will be emailed to the user.

The token and the link will only be valid for a set period of time (24 hours in this example).

4. forgotPassword.html

The first page in the process is the “I forgot my password” page – where the user is prompted for their email address in order for the actual reset process to start.

So – let’s craft a simple forgotPassword.html asking the user for an email address:

<html>
<body>
    <h1 th:text="#{message.resetPassword}">reset</h1>

    <label th:text="#{label.user.email}">email</label>
    <input id="email" name="email" type="email" value="" />
    <button type="submit" onclick="resetPass()" 
      th:text="#{message.resetPassword}">reset</button>

<a th:href="@{/registration.html}" th:text="#{label.form.loginSignUp}">
    registration
</a>
<a th:href="@{/login}" th:text="#{label.form.loginLink}">login</a>

<script src="jquery.min.js"></script>
<script th:inline="javascript">
var serverContext = [[@{/}]];
function resetPass(){
    var email = $("#email").val();
    $.post(serverContext + "user/resetPassword",{email: email} ,
      function(data){
          window.location.href = 
           serverContext + "login?message=" + data.message;
    })
    .fail(function(data) {
        if(data.responseJSON.error.indexOf("MailError") > -1)
        {
            window.location.href = serverContext + "emailError.html";
        }
        else{
            window.location.href = 
              serverContext + "login?message=" + data.responseJSON.message;
        }
    });
}

</script>
</body>

</html>

We now need to link to this new “reset password” page from the login page:

@GetMapping("/user/changePassword")
public String showChangePasswordPage(Locale locale, Model model, 
  @RequestParam("token") String token) {
    String result = securityService.validatePasswordResetToken(token);
    if(result != null) {
        String message = messages.getMessage("auth.message." + result, null, locale);
        return "redirect:/login.html?lang=" 
            + locale.getLanguage() + "&message=" + message;
    } else {
        model.addAttribute("token", token);
        return "redirect:/updatePassword.html?lang=" + locale.getLanguage();
    }
}

And here is the validatePasswordResetToken() method:

public String validatePasswordResetToken(String token) {
    final PasswordResetToken passToken = passwordTokenRepository.findByToken(token);

    return !isTokenFound(passToken) ? "invalidToken"
            : isTokenExpired(passToken) ? "expired"
            : null;
}

private boolean isTokenFound(PasswordResetToken passToken) {
    return passToken != null;
}

private boolean isTokenExpired(PasswordResetToken passToken) {
    final Calendar cal = Calendar.getInstance();
    return passToken.getExpiryDate().before(cal.getTime());
}

And here is the changeUserPassword() method:

public void changeUserPassword(User user, String password) {
    user.setPassword(passwordEncoder.encode(password));
    repository.save(user);
}

And the PasswordDto:

public class PasswordDto {

    private String oldPassword;

    private  String token;

    @ValidPassword
    private String newPassword;
}

8. Conclusion

In this article, we implemented a simple but very useful feature for a mature Authentication process – the option to reset your own password, as a user of the system.

The full implementation of this tutorial can be found in the GitHub project – this is an Eclipse based project, so it should be easy to import and run as it is.