Implement password Reset feature for your java application

Grettings!

In this article I'll show you how you can implement a password reset feature in your java application.
You have to follow this {var} simple steps to implement this feature.

1. Create a model for validation token

You can create a parent class ValidationToken that you can extend in any specific type of validation token class.



Extend it to AcValidationToken class that you are going to use for this purpose.

@MappedSuperclass public class ValidationToken extends BaseEntity { private String token; private boolean tokenValid; // getters and setters }


Extend it to AcValidationToken class that you are going to use for this purpose.

@Entity
public class AcValidationToken extends ValidationToken{
    @OneToOne   
     private User user;    
     private String reason;
    // getters and setters
}
Here we've created a @OneToOne relationship with user because we are going to reset password for that user.

2. Repository and Service for that model

AcValidationTokenRepository.java

@Repository public interface AcValidationTokenRepository extends JpaRepository<AcValidationToken, Long> { AcValidationToken findByToken(String token); }


AcValidationTokenServiceImpl.java

@Service public class AcValidationTokenServiceImpl implements AcValidationTokenService { private final AcValidationTokenRepository tokenRepo; @Autowired public AcValidationTokenServiceImpl(AcValidationTokenRepository tokenRepo) { this.tokenRepo = tokenRepo; } @Override public AcValidationToken save(AcValidationToken acValidationToken) { return this.tokenRepo.save(acValidationToken); } @Override public AcValidationToken findOne(Long id) { return this.tokenRepo.findOne(id); } @Override public AcValidationToken findByToken(String token) { if (token == null) return null; return this.tokenRepo.findByToken(token); } @Override public void delete(Long id) { this.tokenRepo.delete(id); } @Override public boolean isTokenValid(String token) { if (token == null || token.isEmpty()) return false; AcValidationToken acValidationToken = this.findByToken(token); return acValidationToken != null && acValidationToken.isTokenValid(); } }


3. Create a token generator utility class


We'll use this class to generate a unique token.

public class SessionIdentifierGenerator { private SecureRandom random = new SecureRandom(); public String nextSessionId() { return new BigInteger(130, random).toString(32); } public String nextPassword(){ return new BigInteger(130, random).toString(32); } }


4. Create Service Methods

So let's create two service methods for this process. First one is responsible for generating validation token and email it to the user.

This method will take and email where it'll send the token and validationUrl to let user click that link to verify their email. if validationUrl is null then it'll just send the token. (useful when you are prompting user to enter the token to verify after sending the email)

@Override public void requireAccountValidationByEmail(String email, String validationUrl) throws UserNotFoundException { if (email == null) throw new IllegalArgumentException("Email invalid!"); User user = this.findByEmail(email); SessionIdentifierGenerator sessionIdentifierGenerator = new SessionIdentifierGenerator(); AcValidationToken acValidationToken = new AcValidationToken(); acValidationToken.setToken(sessionIdentifierGenerator.nextSessionId()); acValidationToken.setTokenValid(true); acValidationToken.setUser(user); // save acvalidationtoken acValidationToken = this.acValidationTokenService.save(acValidationToken); if (validationUrl == null) { this.mailService.sendEmail(user.getEmail(), "Verification token", "Your verification token is: " + acValidationToken.getToken()); return; } // build confirmation link String confirmationLink = baseUrlApi.trim() + validationUrl + "?token=" + acValidationToken.getToken() + "&enabled=true"; // send link by email this.mailService.sendEmail(user.getEmail(), "Please verify you account", "Please verify your email by clicking this link " + confirmationLink); }

This method will be used to reset users password. It takes two arguments. token  and password (new password). This method will check if token is valid and reset password if it's vaid.

@Override @Transactional public User resetPassword(String token, String password) throws NullPasswordException, UserAlreadyExistsException, UserInvalidException { if (password.length() < 6) throw new IllegalArgumentException("Password length should be at least 6"); AcValidationToken acValidationToken = this.acValidationTokenService.findByToken(token); if(!acValidationTokenService.isTokenValid(token)) throw new UserInvalidException("Token invalid"); User user = acValidationToken.getUser(); user.setPassword(PasswordUtil.encryptPassword(password, PasswordUtil.EncType.BCRYPT_ENCODER, null)); acValidationToken.setTokenValid(false); acValidationToken.setReason("Password Reset"); user = this.save(user); acValidationToken.setUser(user); this.acValidationTokenService.save(acValidationToken); return user; }

Okay now, in our controller we have to create these four routes. 

Send a password reset email

// Password reset @PostMapping("/resetPassword/verifyEmail") private ResponseEntity verifyEmail(@RequestParam("email") String email) throws UserNotFoundException { this.userService.requireAccountValidationByEmail(email, null); return ResponseEntity.status(HttpStatus.OK).build(); }

So if you send a request to this endpoint it'll send you an email with a verification token.

Check Token Validity

@PostMapping("/checkTokenValidity") private ResponseEntity checkTokenValidity(@RequestParam(value = "token") String token) { if (!this.acValidationTokenService.isTokenValid(token)) return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); return ResponseEntity.ok(token); }

this endpoint is for checking the token validity. Remember to send a request to this method before reseting password. But don't worry, we have extra protection in out resetPassword() service method.  It'll check again if this token is valid or not. And will throw an exception if invalid.

Now reset the password

@PostMapping("/resetPassword") private ResponseEntity resetPassword(@RequestParam("token") String token, @RequestParam("password") String password) throws Exception, NullPasswordException, UserAlreadyExistsException, UserInvalidException { if (!this.acValidationTokenService.isTokenValid(token)) return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); User user = this.userService.resetPassword(token, password); return ResponseEntity.ok(user); }

Wholla!! you are up!

Since this process needs to send emails, so if you want you can take a look at this article about how you can send email from spring application.

Please let me know if you have any suggestions or improvement ideas during that process. I'm happy to learn new things and ideas.




Comments

Post a Comment

Popular posts from this blog

Deploy Spring Boot app in digitalocean cloud (or any cloud as long asyou have ssh access)

Upload large files : Spring Boot

User activity logging: Spring