Spring Security: Forgot Password Functionality

Introduction

The internet is becoming more and more service oriented with more businesses and companies coming up with offerings that can be provided or accessed online. This requires users to create many accounts on many different platforms for the services that they get online. Such services range from online shopping to subscription-based services, such as music and entertainment offerings and also educational services including courses and tutorial packs.

Internet users end up creating many different accounts for different platforms, and using the same password across services is not recommended. This brings along the burden of remembering multiple different passwords for multiple accounts and, unfortunately, some seep through and are forgotten because, after all, we are human.

Forgetting passwords is a real issue that users face and, as system and platform developers, we can make it easier for our users in managing their passwords by offering functionality that allows them to reset their passwords securely in case they forget them. This will help enhance the retention of customers on our platform since they can be confident that in the event they lost their password, they will not have lost their account.

In this post, we will explore how Spring Security helps us not only secure our Spring-based applications, but also help our users recover their lost passwords in an easy and secure way.

Securing Web Applications with Spring Security

Spring Security is a framework that is easy-to-extend and customize and it is centered around the provision of authentication and access-control facilities for Spring-based applications. It handles authentication and authorization and also helps secure Java applications against common security vulnerabilities and attacks such as session fixation, clickjacking and cross-site request forgery among others.

Spring Security can also be used to simplify account management facilities in Java Enterprise application through features such as the provision of OAuth2 authorization framework to allow users to use third-party platforms to identify themselves in our Java applications.

This is most commonly implemented through Social Login where we can use our accounts on platforms such as Facebook, Twitter, Google, and GitHub to access and identify ourselves to other platforms.

OpenID is an authentication protocol promoted by the OpenID Foundation that is decentralized and standard, which can be used to sign in on multiple websites without having to create new passwords. OpenID is supported by Spring Security and it can be used to ease the registration and access to our Java applications for our end users.

In the case of organizations that use LDAP (Lightweight Directory Access Protocol) protocol as an authentication service and central repository of user information, Spring Security provides the functionality to integrate LDAP in your Spring-based application.

This will allow the current members of the organizations to securely access new applications with their existing credentials without having to create a whole new set of credentials.

Password Management in Spring Security

We have seen a subset of the functionality that Spring Security offers and it does even more for our passwords. Spring Security takes a user's credentials and converts them into a token that is passed into an AuthenticationManager instance to validate the credentials. In case the password is wrong, Spring Security will provide feedback on the error and the process will not proceed past that point.

After validation, a security context is established and now the user is considered authenticated. The security context also defines the user's role in the system and can be used to determine the level of access that is allowed whether as an administrator or normal user.

Spring Security uses a PasswordEncoder interface to encode or transform passwords to facilitate the secure storage of the credentials. This encoded password is supposed to be one way and the verification process involves comparing the password the user has provided with the one that is stored, and if they match, the details are correct.

In order to enhance the security of user's passwords, Spring Security allows developers to use one-way functions (or hashes) to encode passwords such as Bcrypt, Argon2, Scrypt, and PBKDF2. These functions are resource intensive but their purpose is to be one-way and to make it harder for attackers to crack and extract users' credentials.

If you'd like to read more on this topic, you can check out the article Password Encoding with Spring Security.

Password storage practices will keep changing over time to enhance the security of the current methods and it is for this reason that Spring Security introduced the DelegatingPasswordEncoder interface in version 5.0+.

It ensures that password encoding utilizes the currently recommended methods for password storage and allows for the upgrade of the encoding functions in the future. It also allows us to use different encoding functions for different passwords and this can be distinguished by using an identifier prefixed on the encoded password. For example, if we used bcrypt to encode our passwords the output would be, for example:

{bcrypt}$2a$12$rBvYrRneJjT/pmXakMbBg.vA1jUCpEMPE5z2tY3/4kyFw.KoiZA6C

In the absence of the identifying prefix, a default encoding function is used.

This is how Spring handles passwords and security in general, but what happens when our users forget their credentials?

Sending Emails in Spring Boot

Emails are widely used by systems and platforms to identify users and to also send communications and promotions to them. This makes email addresses important to an internet user's identity who ends up having not so many email accounts for personal use. This means that a user's email account is easily and readily accessible to them since they use it frequently. Email is, therefore, among the best avenues to use to help users recover their passwords.

In order to allow our users to reset their passwords, our Spring-based application should be able to dispatch emails to users. Through these emails, we can provide tokens or links or instructions to our users detailing how they can recover their accounts.

Java provides the javax.mail library, which is also known as JavaMail, that we can use to send emails to our users whenever they forget their passwords. The JavaMail library allows us to compose and send emails via various protocols such as SMTP (Simple Mail Transfer Protocol), POP (Post Office Protocol) or IMAP (Internet Message Access Protocol).

It allows us to send plain text emails as well as messages containing HTML, which makes our emails catchy and appealing to the eye. We can even send emails with attachments when using the JavaMail library.

If you are interested in learning more about sending emails in Java, we've got it covered.

Other libraries you can use to send emails in Java include:

Implementation

Let us now bring all of the above together into a project and help our users recover their passwords with ease.

This article's main goal was to provide guidance on reset password functionality. Before we can reset passwords, we need to allow users to register, confirm their accounts via email and let them log in to their confirmed accounts.

The article Spring Security: Email Verification Registration covers user registration and confirmation of accounts via a token sent to the user's email. In the spirit of keeping this article focused on the password reset functionality, we will fork and extend this project on GitHub that is based on that article and add our new functionality which includes login and reset password functionality.

Recap

The project uses the Spring framework alongside Spring Security coupled with Thymeleaf as the server-side templating engine. Hibernate is used to interact with a MySQL database to save the users' details.

A user accesses the system and is immediately prompted to register by providing their details including their names, email address for verification and a password. In the following sections, we will pick up from there and add login functionality first, then add reset password functionality to help our users reset their passwords.

Changes and Additions

There are a few things we'll change in this project and I'll highlight them as we make progress. First, we will update the Spring version from 1.5.4 to 2.1.4.RELEASE in our pom.xml. We will also update the version of the MySQL connector to version 8.0.13 to have the project run smoothly with the updated Spring version.

While the old version still works, updating the dependencies ensures that we can tap into new functionality that has been introduced in the later version. Also, some problems we might face will be eliminated through the use of updated packages.

We will need to set up our local database and update the applications.properties file to suit our new setup. We will also need to update the email settings to facilitate the sending of emails:

# add these new properties
spring.mail.transport.protocol=smtp
spring.mail.from.email=<your-email-goes-here>

# modify these properties with your credentials
spring.mail.username=<your-email-goes-here>
spring.mail.password=<password-goes-here>

# update our database configuration
spring.datasource.url=jdbc:mysql://localhost:3306/demodb?allowPublicKeyRetrieval=true&useSSL=false
spring.datasource.username=<database-username>
spring.datasource.password=<database-password>

With these settings modified, we can run the project and register as new users. The email provided in the application.properties will appear as the sender of the email containing the confirmation token.

Login Functionality

We can register and confirm our account via email at this point. Before we can reset our passwords, we should be able to log in, and when we forget our password, we should be able to reset it. To implement the login functionality, we will start by creating the templates, then the view on the controller.

Our login page will have fields for the email and password, and a hidden section to display any messages when needed. Such messages can include notifying the user when they provide invalid credentials or when the user does not exist in the system. We will also create an additional template to be displayed on successful login, which will serve as our homepage.

In our templates folder, we add the login page:

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head> <title>Login</title> </head>
    <body>
        <center> <span th:text="${message}"></span> <br/> </center>
        <center>
            <form action="#" th:action="@{/login}" th:object="${user}" method="post">
                <table>
                    <tr>
                        <td><label for="emailId">Email</label></td>
                        <td><input th:field="*{emailId}" type="text" name="emailId"></input></td>
                    </tr>
                    <tr>
                        <td><label for="password">Password</label></td>
                        <td><input th:field="*{password}" type="password" name="password"></input></td>
                    </tr>
                    <tr>
                        <td><input type="reset" value="Clear"/></td>
                        <td><input type="submit" value="Submit"></input></td>
                    </tr>
                </table>
            </form>

            <a href="/forgot-password">Forgot Password?</a>
        </center>
    </body>
</html>

This is a form that takes in an email address and password and sends that information to our controller at the /login endpoint for verification. There is also a Forgot Password? link, which we'll implement later.

After successful login, we notify the user and redirect them to the homepage, which in our case will simply be successLogin.html:

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
  xmlns:th="http://www.thymeleaf.org">
    <head> <title>Login Success</title> </head>
    <body>
        <center> <span th:text="${message}"></span> </center>
    </body>
</html>

Let us extend our UserAccountController which is located in the controller folder to include login functionality.

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

First, we will import the BCryptPasswordEncoder class to encode our passwords and instantiate them in order to encrypt and compare our passwords. Initially, the project saved raw passwords in the database, we will modify this to have passwords encrypted when the user is registering since saving raw passwords is not good practice.

For the login functionality, we will implement a function to display the page with the login form, and another function to receive the credentials, verify them and either notify of any issues or take the user to the next page on success. If the email provided does not exist in our database, we will also notify the user.

BCryptPasswordEncoder provides the matches(rawPassword, encodedPassword) function to help us compare the provided password with the one we have in the records. We use this method instead of encoding the raw password and comparing since a different salt is used every time and a direct comparison would fail all the time.

First we add the new import:

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

And then include these changes:

    // Instantiate our encoder
    BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12);

    // Right before saving the user on registration, we encode the password
    user.setPassword(encoder.encode(user.getPassword()));
    userRepository.save(user);

    // Function to display login page
    @RequestMapping(value="/login", method=RequestMethod.GET)
    public ModelAndView displayLogin(ModelAndView modelAndView, User user) {
        modelAndView.addObject("user", user);
        modelAndView.setViewName("login");
        return modelAndView;
    }

    // Function to handle the login process
    @RequestMapping(value="/login", method=RequestMethod.POST)
    public ModelAndView loginUser(ModelAndView modelAndView, User user) {
        User existingUser = userRepository.findByEmailIdIgnoreCase(user.getEmailId());
        if (existingUser != null) {
            // Use encoder.matches to compare raw password with encrypted password

            if (encoder.matches(user.getPassword(), existingUser.getPassword())){
                // Successfully logged in
                modelAndView.addObject("message", "Successfully logged in!");
                modelAndView.setViewName("successLogin");
            } else {
                // Wrong password
                modelAndView.addObject("message", "Incorrect password. Try again.");
                modelAndView.setViewName("login");
            }
        } else {
            modelAndView.addObject("message", "The email provided does not exist!");
            modelAndView.setViewName("login");
        }
        return modelAndView;
    }

When we run our project this is the resulting page when we navigate to the /login endpoint:

If the credentials are wrong, a message containing the error is displayed on top of the form, and if they are valid, the user is redirected to a page showing a success message.

Now our user can log in with their credentials, but what happens when they forget them? The Forgot Password? link comes to the rescue.

Forgot Password Functionality

When a user forgets their password, they can request to have it reset by clicking on the Forgot Password? link. The user will be prompted to provide the email address they registered with and a token will be generated and sent to the email address as part of the link.

Once the user clicks on the reset link, we will validate the token and redirect the user to a page where they can enter a new password for their account. We will now save this new password having confirmed that the user has access to the email address they provided. They will now be able to log in with the updated credentials.

We will create the forgot password template where the user will enter their email address through the forgotPassword.html template:

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
 xmlns:th="http://www.thymeleaf.org">
    <head> <title>Forgot Password</title> </head>
    <body>
        <center>
            <form action="#" th:action="@{/forgot-password}" th:object="${user}" method="post">
                <table>
                    <tr>
                        <td><label for="emailId">Email</label></td>
                        <td><input th:field="*{emailId}" type="text" name="emailId"></input></td>
                    </tr>
                    <tr>
                        <td><input type="reset" value="Clear"/></td>
                        <td><input type="submit" value="Reset Password"></input></td>
                    </tr>
                </table>
            </form>
        </center>
    </body>
</html>

We will create an additional successForgotPassword.html template to display the success message when the password has been successfully reset and this is present in the full codebase linked below.

With the template in place, let us update our UserAccountController to handle this new functionality. We will have a function to display the form, and another to receive the email, create a token, and send an email to the user with the link to reset the user's password.

The additions to our controller include:

    // Display the form
    @RequestMapping(value="/forgot-password", method=RequestMethod.GET)
    public ModelAndView displayResetPassword(ModelAndView modelAndView, User user) {
        modelAndView.addObject("user", user);
        modelAndView.setViewName("forgotPassword");
        return modelAndView;
    }

    // Receive the address and send an email
    @RequestMapping(value="/forgot-password", method=RequestMethod.POST)
    public ModelAndView forgotUserPassword(ModelAndView modelAndView, User user) {
        User existingUser = userRepository.findByEmailIdIgnoreCase(user.getEmailId());
        if (existingUser != null) {
            // Create token
            ConfirmationToken confirmationToken = new ConfirmationToken(existingUser);

            // Save it
            confirmationTokenRepository.save(confirmationToken);

            // Create the email
            SimpleMailMessage mailMessage = new SimpleMailMessage();
            mailMessage.setTo(existingUser.getEmailId());
            mailMessage.setSubject("Complete Password Reset!");
            mailMessage.setFrom("[email protected]");
            mailMessage.setText("To complete the password reset process, please click here: "
              + "http://localhost:8082/confirm-reset?token="+confirmationToken.getConfirmationToken());

            // Send the email
            emailSenderService.sendEmail(mailMessage);

            modelAndView.addObject("message", "Request to reset password received. Check your inbox for the reset link.");
            modelAndView.setViewName("successForgotPassword");

        } else {
            modelAndView.addObject("message", "This email address does not exist!");
            modelAndView.setViewName("error");
        }
        return modelAndView;
    }

We can now package and run our project again by using the mvn spring-boot:run command. When we click on the Forgot Password? link, we can see a form with an email field. When we fill in our registered email address, we receive the following email:

So far, we have been able to receive a password reset request and sent an email to the user with a link to reset their password.

To implement the next part of the password reset functionality, we will need to create a template that receives the user's new password. It will resemble the login page the only major difference being the email field will be read-only.

The new template resetPassword:

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
 xmlns:th="http://www.thymeleaf.org">
    <head> <title>Reset Password</title> </head>
    <body>
        <center>
            <h2>Enter new password:</h2>
            <form action="#" th:action="@{/reset-password}" th:object="${user}" method="post">
                <table>
                    <tr>
                        <td><label for="emailId">Email</label></td>
                        <td><input th:field="*{emailId}" type="text" name="emailId" readonly></input></td>
                    </tr>
                    <tr>
                        <td><label for="password">Password</label></td>
                        <td><input th:field="*{password}" type="password" name="password"></input></td>
                    </tr>
                    <tr>
                        <td><input type="reset" value="Clear"/></td>
                        <td><input type="submit" value="Submit"></input></td>
                    </tr>
                </table>
            </form>
        </center>
    </body>
</html>

We will pre-fill the user's email address in a read-only field then let the user fill in their new password.

Two new endpoints will be introduced at this point:

  • /confirm-reset: handles the verification of the token and on success will redirect the user to the next endpoint
  • /reset-password: displays the form above, receives the new credentials, and update them in the database

Let us add these new endpoints in our controller as follows:

    // Endpoint to confirm the token
    @RequestMapping(value="/confirm-reset", method= {RequestMethod.GET, RequestMethod.POST})
    public ModelAndView validateResetToken(ModelAndView modelAndView, @RequestParam("token")String confirmationToken) {
        ConfirmationToken token = confirmationTokenRepository.findByConfirmationToken(confirmationToken);

        if (token != null) {
            User user = userRepository.findByEmailIdIgnoreCase(token.getUser().getEmailId());
            user.setEnabled(true);
            userRepository.save(user);
            modelAndView.addObject("user", user);
            modelAndView.addObject("emailId", user.getEmailId());
            modelAndView.setViewName("resetPassword");
        } else {
            modelAndView.addObject("message", "The link is invalid or broken!");
            modelAndView.setViewName("error");
        }
        return modelAndView;
    }

    // Endpoint to update a user's password
    @RequestMapping(value = "/reset-password", method = RequestMethod.POST)
    public ModelAndView resetUserPassword(ModelAndView modelAndView, User user) {
        if (user.getEmailId() != null) {
            // Use email to find user
            User tokenUser = userRepository.findByEmailIdIgnoreCase(user.getEmailId());
            tokenUser.setPassword(encoder.encode(user.getPassword()));
            userRepository.save(tokenUser);
            modelAndView.addObject("message", "Password successfully reset. You can now log in with the new credentials.");
            modelAndView.setViewName("successResetPassword");
        } else {
            modelAndView.addObject("message","The link is invalid or broken!");
            modelAndView.setViewName("error");
        }
        return modelAndView;
    }

With these new changes, we can run the project and click on the link that came in the password reset email that was sent earlier. The result is:

When we enter our new password, we get a success message. Our password has been updated and we can test this new password by navigating to the login page and logging in with the new credentials.

It works! Our users can now reset their forgotten passwords through links sent to their email address from our Spring Boot web application.

Conclusion

We have learned of the various ways Spring Security can provide authentication and access-control facilities to help us secure our Spring-based applications in an easily extensible and customizable way.

We have also understood how Spring Security handles our users' passwords through various algorithms to securely encode and store the password such that it is indecipherable to an attacker. We briefly highlighted how we can send emails in Spring and in our case used this knowledge to send emails to confirm our users' accounts when they are registering and also recovering their account. This email functionality can also be used when sending login notifications or flagging of suspicious activity in the users' accounts.

Finally, we extended a Spring email registration project by adding login and reset password functionality to help our users in case they cannot remember their credentials.

The full and final codebase of this project is available here on GitHub.

Last Updated: August 15th, 2023
Was this article helpful?

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms