Spring Boot Forgot Password Example
This guide will help you to create the Spring Boot REST API for forgot password. The password recovery feature is very important when the application has multiple users. It helps the user to recover/update the password when the user forgot it to access the application again.
Note: In this article, the password is stored in plain text for DEVELOPMENT and TESTING purposes only. The password must be
hashed
in the PRODUCTION environment. You can use Spring Security Password Storage for hashing the password.
What we’ll build
In this tutorial, we’ll build Spring Boot RESTful service to recover/update the user password. It brings the following features:
1. Validate user by email id.
Optional<User> userOptional = Optional
.ofNullable(userRepository.findByEmail(email));
if (!userOptional.isPresent()) {
return "Invalid email id.";
}
2. Generate unique token
private String generateToken() {
StringBuilder token = new StringBuilder();
return token.append(UUID.randomUUID().toString())
.append(UUID.randomUUID().toString()).toString();
}
3. Generate the password recovery URL
String response = userService.forgotPassword(email);
if (!response.startsWith("Invalid")) {
response = "http://localhost:8080/reset-password?token=" + response;
}
4. Validate the token
Optional<User> userOptional = Optional
.ofNullable(userRepository.findByToken(token));
if (!userOptional.isPresent()) {
return "Invalid token.";
}
if (isTokenExpired(tokenCreationDate)) {
return "Token expired.";
}
5. Update the new password
User user = userOptional.get();
user.setPassword(password);
user.setToken(null);
user.setTokenCreationDate(null);
userRepository.save(user);
return "Your password successfully updated.";
Technologies Used
Find the list of all technologies used in this application.
- STS 4
- JDK 8
- Spring Boot 2.3.0.RELEASE
- MySQL database
Dependencies Required
To make it work, make sure these following dependencies are available in your build path. Add the following to your pom.xml.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
Project Structure
The final project structure of our application in STS ide will look like as follows:
MySQL Database Schema
Find the table structure and the insert script of the MySQL database used in this example.
create table `user` (
`id` bigint ,
`email` varchar ,
`name` varchar ,
`password` varchar ,
`token` varchar ,
`token_creation_date` timestamp
);
insert into `user` (`id`, `email`, `name`, `password`, `token`, `token_creation_date`) values('1','[email protected]','Atul Rai','abcd@123','76cdc01e-f5bb-49dd-ba55-9bcc0e8681c8868f0ce7-00ec-4766-9e6a-067238fc715b','2020-05-23 22:00:04');
insert into `user` (`id`, `email`, `name`, `password`, `token`, `token_creation_date`) values('2','[email protected]','Prince kumar','MyFavCountry@123',NULL,NULL);
insert into `user` (`id`, `email`, `name`, `password`, `token`, `token_creation_date`) values('3','[email protected]','Manish Fartiyal','manish123',NULL,NULL);
application.properties
Configure data source, JPA properties, etc in the application.properties file.
# MySQL database connection strings
spring.datasource.url=jdbc:mysql://localhost:3306/spring_dev
spring.datasource.username=root
spring.datasource.password=root
# JPA property settings
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.show_sql=true
User Entity
The User
entity class will create the table in MySQL database as shown in the above script. It will be used with the UserRepository
interface later to perform the database operation using Spring Data JPA.
package org.websparrow.entity;
import java.time.LocalDateTime;
import javax.persistence.*;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Column(unique = true)
private String email;
private String password;
private String token;
@Column(columnDefinition = "TIMESTAMP")
private LocalDateTime tokenCreationDate;
// Generate Getters and Setters...
}
User Repository
UserRepository
interface extends the JpaRepository
interface of the package org.springframework.data.jpa.repository
.
package org.websparrow.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.websparrow.entity.User;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email);
User findByToken(String token);
}
User Service
The UserService
class communicates with the database and validates the user information like email id, token & token expiry. If all the validation successfully passed, it will update the new user password and respond accordingly.
package org.websparrow.service;
import java.time.*;
import java.util.*;
import org.springframework.*;
import org.websparrow.entity.User;
import org.websparrow.repository.UserRepository;
@Service
public class UserService {
private static final long EXPIRE_TOKEN_AFTER_MINUTES = 30;
@Autowired
private UserRepository userRepository;
public String forgotPassword(String email) {
Optional<User> userOptional = Optional
.ofNullable(userRepository.findByEmail(email));
if (!userOptional.isPresent()) {
return "Invalid email id.";
}
User user = userOptional.get();
user.setToken(generateToken());
user.setTokenCreationDate(LocalDateTime.now());
user = userRepository.save(user);
return user.getToken();
}
public String resetPassword(String token, String password) {
Optional<User> userOptional = Optional
.ofNullable(userRepository.findByToken(token));
if (!userOptional.isPresent()) {
return "Invalid token.";
}
LocalDateTime tokenCreationDate = userOptional.get().getTokenCreationDate();
if (isTokenExpired(tokenCreationDate)) {
return "Token expired.";
}
User user = userOptional.get();
user.setPassword(password);
user.setToken(null);
user.setTokenCreationDate(null);
userRepository.save(user);
return "Your password successfully updated.";
}
/**
* Generate unique token. You may add multiple parameters to create a strong
* token.
*
* @return unique token
*/
private String generateToken() {
StringBuilder token = new StringBuilder();
return token.append(UUID.randomUUID().toString())
.append(UUID.randomUUID().toString()).toString();
}
/**
* Check whether the created token expired or not.
*
* @param tokenCreationDate
* @return true or false
*/
private boolean isTokenExpired(final LocalDateTime tokenCreationDate) {
LocalDateTime now = LocalDateTime.now();
Duration diff = Duration.between(tokenCreationDate, now);
return diff.toMinutes() >= EXPIRE_TOKEN_AFTER_MINUTES;
}
}
User Controller
UserController
class handles the user request and responds accordingly.
package org.websparrow.controller;
import org.springframework.*;
import org.websparrow.service.UserService;
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/forgot-password")
public String forgotPassword(@RequestParam String email) {
String response = userService.forgotPassword(email);
if (!response.startsWith("Invalid")) {
response = "http://localhost:8080/reset-password?token=" + response;
}
return response;
}
@PutMapping("/reset-password")
public String resetPassword(@RequestParam String token,
@RequestParam String password) {
return userService.resetPassword(token, password);
}
}
Execute it
package org.websparrow;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootPasswordResetApp {
public static void main(String[] args) {
SpringApplication.run(SpringBootPasswordResetApp.class, args);
}
}
Test it
Let’s test the application. To test the application open the Postman and follow the below steps:
Step 1: Hit this below endpoint in your Postman client by providing your email id:
Endpoint: http://localhost:8080/forgot-password
HTTP method: POST
Response:
1. Invalid email id: when you enter invalid email id.
2. Password reset endpoint: if the email is valid.
Step 2: Use the provided endpoint with your new password:
Endpoint: http://localhost:8080/reset-password?token=<some-unique-token>
HTTP method: PUT
Response:
1. Invalid token: if the token is not valid.
2. Token expired: if the token is expired (Expire threshold value is 30 minutes)
3. Your password successfully updated: when all validation is passed.
Download Source Code: spring-boot-forgot-password-example.zip
References
- Spring Data CrudRepository Interface Example
- Spring Data JPA Derived Query Methods Example
- Spring Boot RESTful CRUD Example with MySQL Database