I just announced the newSpring Security 5 modules (primarily focused on OAuth2) in the course:

>> CHECK OUT LEARN SPRING SECURITY


This article is part of a series:
Spring Security Registration TutorialThe Registration Process With Spring SecurityRegistration – Activate a New Account by Email
Spring Security Registration – Resend Verification Email
Registration with Spring Security – Password EncodingThe Registration API becomes RESTfulSpring Security – Reset Your PasswordRegistration – Password Strength and RulesUpdating your Password

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:

@RequestMapping(value = "/user/resendRegistrationToken", method = RequestMethod.GET)
@ResponseBody
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:

@RequestMapping(value = "/regitrationConfirm", method = RequestMethod.GET)
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.

I just announced the new Spring Security 5 modules (primarily focused on OAuth2) in the course:

>> CHECK OUT LEARN SPRING SECURITY

Sort by:   newest | oldest | most voted
Ína Be
Guest

Hi how to configure the JavaMailSender? i would like to use the @bean annotation and not the xml

Eugen Paraschiv
Guest

Hey Ina – here’s the configuration of that bean over on github. Hope it helps. Cheers,
Eugen.

Ína Be
Guest

Thx!! 🙂 really fast answer!

Eugen Paraschiv
Guest

Sure thing, happy to help.

Ína Be
Guest

Hi Eugen 🙂 , how can i test my mail service? like best practice? Thx!

Eugen Paraschiv
Guest

Well, testing an email service is difficult. You have a few options – you can either use a fake SMTP service that is specifically built for helping you test the email side of things. Or you can mock that and simply test the interaction. Mocking is of course the simpler router, so I’d suggest that unless you have strict end-to-end testing requirements about testing these external facing services.
Hope that helps. Cheers,
Eugen.

Wim Deblauwe
Guest

I learned from a co-worker you can use Mailtrap to test SMTP: https://mailtrap.io/

Momcilo Davidovic
Guest

https://nilhcem.github.io/FakeSMTP/ is very easy to use and very helpful.

Admilson Cossa
Guest

Hi nice post, so the java Mail configuration proprieties still exposes the password which will result in authentication failure accordingly to google, so how can I set up it to use XOAUTH2 mechanisms using spring security or spring security oauth2? Thanks!

Admilson Cossa
Guest

Well probably I’ll have to consume the Gmail API using client credentials grant type and create a CustomMailService implementation then use JavaMail from javax.mail instead of JavaMailImpl from spring. Yet, still don’t understand how can I use JavaMailImpl with spring security oauth2 or just spring security with XOAUTH2 mechanisms

Eugen Paraschiv
Guest

As far as I remember, I was able to send emails through Gmail OK with this exact configuration.
I did switch to an SMTP server that runs on Amazon SES so I haven’t tried it very recently though.
However, while that’s a good question, it’s slightly outside the scope of the article – because focus here is higher level – how to send the verification email, not how to set up the low level SMTP (the assumption is that you have that and can send email). Hope that helps. Cheers,
Eugen.

Admilson Cossa
Guest

Okay, I was able to send email using this configuration too, but recently I was getting google exception which led me to change my google accounts settings to allow less secure apps to access my account. That’s why I’ve asked this right here. Thank you 🙂

Eugen Paraschiv
Guest

Glad everything worked out. Cheers,
Eugen.

Jim Clayson
Guest

Hey Eugen, thanks for sharing this.

I was wondering, is there a specific reason for using an ajax call to request a token resend?

Am I right in saying one could just as well have used an html form or anchor tag or javascript to send the request?

Eugen Paraschiv
Guest

Hey Jim,
Sure, you can trigger that operation in all of those ways. The reason I went with an AJAX here is that I wanted to move away form the MVC style of application and towards a more RESTful approach.
Hope that clears things up. Cheers,
Eugen.

Jim Clayson
Guest

Sure, thanks.

What about the exception handling code? Is that specific to these ajax-style client calls? Or can it be used to service non-ajax calls, too?

Eugen Paraschiv
Guest

Well, the differences between the way an API deals with exceptions (HTTP codes, JSON responses) and the way exceptions are done in MVC logic – are significant.
So, no – you’ll have to decide what you want to go with, and then write idiomatic code that fits into the architecture you selected.
Hope that clears things up. Cheers,
Eugen.

wpDiscuz