Table of Contents
1. Overview
In this tutorial – we’re continuing the ongoing Registration with Spring Security series with a look at resending the verification link to the user in case it expires before they have a chance to activate their account.
2. Resend the Verification Link
First, let’s see we what happens when the user requests another verification link, in case the previous one expired.
First – we’ll reset the existing token with a new expireDate. The, we’ll send the user a new email, with the new link/token:
@GetMapping("/user/resendRegistrationToken") public GenericResponse resendRegistrationToken( HttpServletRequest request, @RequestParam("token") String existingToken) { VerificationToken newToken = userService.generateNewVerificationToken(existingToken); User user = userService.getUser(newToken.getToken()); String appUrl = "http://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath(); SimpleMailMessage email = constructResendVerificationTokenEmail(appUrl, request.getLocale(), newToken, user); mailSender.send(email); return new GenericResponse( messages.getMessage("message.resendToken", null, request.getLocale())); }
And the utility for actually building the email message the user gets – constructResendVerificationTokenEmail():
private SimpleMailMessage constructResendVerificationTokenEmail (String contextPath, Locale locale, VerificationToken newToken, User user) { String confirmationUrl = contextPath + "/regitrationConfirm.html?token=" + newToken.getToken(); String message = messages.getMessage("message.resendToken", null, locale); SimpleMailMessage email = new SimpleMailMessage(); email.setSubject("Resend Registration Token"); email.setText(message + " rn" + confirmationUrl); email.setFrom(env.getProperty("support.email")); email.setTo(user.getEmail()); return email; }
We also need to modify the existing registration functionality – by adding some new information on the model about the expiration of the token:
@GetMapping("/registrationConfirm") public String confirmRegistration( Locale locale, Model model, @RequestParam("token") String token) { VerificationToken verificationToken = userService.getVerificationToken(token); if (verificationToken == null) { String message = messages.getMessage("auth.message.invalidToken", null, locale); model.addAttribute("message", message); return "redirect:/badUser.html?lang=" + locale.getLanguage(); } User user = verificationToken.getUser(); Calendar cal = Calendar.getInstance(); if ((verificationToken.getExpiryDate().getTime() - cal.getTime().getTime()) <= 0) { model.addAttribute("message", messages.getMessage("auth.message.expired", null, locale)); model.addAttribute("expired", true); model.addAttribute("token", token); return "redirect:/badUser.html?lang=" + locale.getLanguage(); } user.setEnabled(true); userService.saveRegisteredUser(user); model.addAttribute("message", messages.getMessage("message.accountVerified", null, locale)); return "redirect:/login.html?lang=" + locale.getLanguage(); }
3. Exception Handler
The previous functionality is, under certain conditions – throwing exceptions; these exceptions need to be handled, and we’re going to do that with a custom exception handler:
@ControllerAdvice public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler { @Autowired private MessageSource messages; @ExceptionHandler({ UserNotFoundException.class }) public ResponseEntity<Object> handleUserNotFound(RuntimeException ex, WebRequest request) { logger.error("404 Status Code", ex); GenericResponse bodyOfResponse = new GenericResponse( messages.getMessage("message.userNotFound", null, request.getLocale()), "UserNotFound"); return handleExceptionInternal( ex, bodyOfResponse, new HttpHeaders(), HttpStatus.NOT_FOUND, request); } @ExceptionHandler({ MailAuthenticationException.class }) public ResponseEntity<Object> handleMail(RuntimeException ex, WebRequest request) { logger.error("500 Status Code", ex); GenericResponse bodyOfResponse = new GenericResponse( messages.getMessage( "message.email.config.error", null, request.getLocale()), "MailError"); return handleExceptionInternal( ex, bodyOfResponse, new HttpHeaders(), HttpStatus.NOT_FOUND, request); } @ExceptionHandler({ Exception.class }) public ResponseEntity<Object> handleInternal(RuntimeException ex, WebRequest request) { logger.error("500 Status Code", ex); GenericResponse bodyOfResponse = new GenericResponse( messages.getMessage( "message.error", null, request.getLocale()), "InternalError"); return handleExceptionInternal( ex, bodyOfResponse, new HttpHeaders(), HttpStatus.NOT_FOUND, request); } }
Note that:
- we used @ControllerAdvice annotation to handle exceptions across the whole application
- we used a simple object GenericResponse to send the response:
public class GenericResponse { private String message; private String error; public GenericResponse(String message) { super(); this.message = message; } public GenericResponse(String message, String error) { super(); this.message = message; this.error = error; } }
4. Modify badUser.html
We’ll now modify badUser.html by enabling the user to get a new VerificationToken only if their token expired:
<html> <head> <title th:text="#{label.badUser.title}">bad user</title> </head> <body> <h1 th:text="${param.message[0]}">error</h1> <br> <a th:href="@{/user/registration}" th:text="#{label.form.loginSignUp}"> signup</a> <div th:if="${param.expired[0]}"> <h1 th:text="#{label.form.resendRegistrationToken}">resend</h1> <button onclick="resendToken()" th:text="#{label.form.resendRegistrationToken}">resend</button> <script src="jquery.min.js"></script> <script type="text/javascript"> var serverContext = [[@{/}]]; function resendToken(){ $.get(serverContext + "user/resendRegistrationToken?token=" + token, function(data){ window.location.href = serverContext +"login.html?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.html?message=" + data.responseJSON.message; } }); } </script> </div> </body> </html>
Notice that we’ve used some very basic javascript and JQuery here to handle the response of “/user/resendRegistrationToken” and redirect the user based on it.
5. Conclusion
In this quick article we allowed the user to request a new verification link to activate their account, in case the old one expired.
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.