GrabDuck

Writing REST Services in Java: Part 3 Email Verification

:

Previous Post :  Part Two: User sign up and login  Next Post Part Four: Facebook Authentication  
Get the Code:  https://github.com/iainporter/rest-java

In this post I'll discuss the process of verifying an email address after a new sign up. The logic flow for this is:

1. Generate a token for the user and persist it
2. Send an email with an embedded link that includes the token to the user
3. User clicks on the link which takes them to a static page
4. The static page calls the API passing the token
5. If the token is valid the user is set to verified
6. Response returned to the user

The Verification Token Service

This service is responsible for generating tokens, persisting them, communicating with the email services gateway and verifying returned tokens. The main properties of a VerificationToken are:
  • token - a UUID that is used to identify the token. It is Base64 encoded before being sent
  • expiryDate - time to live for the Token. Configured in app.properties
  • tokenType - enum (lostPassword, emailVerification, emailRegistration)
  • verified - has this token been verified
Looking at the method to send an Email Registration token:
    @Transactional
    public VerificationToken sendEmailRegistrationToken(String userId) {
        User user = ensureUserIsLoaded(userId);
        VerificationToken token = new VerificationToken(user,
                VerificationToken.VerificationTokenType.emailRegistration,
                config.getEmailRegistrationTokenExpiryTimeInMinutes());
        user.addVerificationToken(token);
        userRepository.save(user);
        emailServicesGateway.sendVerificationToken(new EmailServiceTokenModel(user,
                token, getConfig().getHostNameUrl()));
        return token;
    }

A new token is generated and persisted.
This token is then sent to the EmailServicesGateway where it is queued and the method returns, leaving the gateway to handle processing of the token asynchronously.

The Email Services Gateway

Email services are implemented with Spring Integration. The context file is at:

 src/main/resources/META-INF/spring/email-services-context.xml

The entry point is the Gateway component which routes to a queue-backed channel. This queue has a message store which in dev and local profiles uses a SimpleMessageStore. For staging and production a datasource is used. I'll cover setting up for production in a later post.

    <int:gateway id="emailServicesGateway" 
           service-interface="com.porterhead.rest.gateway.EmailServicesGateway"
           default-reply-timeout="3000">
    <int:method name="sendVerificationToken" 
            request-channel="emailVerificationRouterChannel" request-timeout="3000"/>
    </int:gateway>

    <int:channel id="emailVerificationRouterChannel">
        <int:queue capacity="1000" message-store="emailVerificationMessageStore"/>
    </int:channel>
A router polls the queue and routes to a channel based on the token type:
<int:router id="emailVerificationRouter" input-channel="emailVerificationRouterChannel"
   expression="payload.getTokenType()">
     <int:poller fixed-rate="2000">
        <int:transactional/>
     </int:poller>
     <int:mapping value="emailVerification" 
          channel="emailVerificationTokenSendChannel"/>
     <int:mapping value="emailRegistration"
          channel="emailRegistrationTokenSendChannel"/>
     <int:mapping value="lostPassword" channel="emailLostPasswordTokenSendChannel"/>
</int:router>

From there the appropriate MailSender method is invoked:
<int:service-activator id="emailRegistrationMailSenderService" 
      input-channel="emailRegistrationTokenSendChannel"
      output-channel="nullChannel" ref="mailSenderService"
      method="sendRegistrationEmail">
</int:service-activator>

The Mail Sender Service

The MailSenderService is responsible for constructing mime messages and sending them to their destination. Velocity is used as the template engine. The templates are in:

 src/main/resources/META-INF/velocity

The handler method:

    private EmailServiceTokenModel sendVerificationEmail(
                final EmailServiceTokenModel emailVerificationModel,
                final String emailSubject, 
                final String velocityModel, 
                final Map<String, String> resources) {
        MimeMessagePreparator preparator = new MimeMessagePreparator() {
            public void prepare(MimeMessage mimeMessage) throws Exception {
              MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage,
              MimeMessageHelper.MULTIPART_MODE_RELATED, "UTF-8");
              messageHelper.setTo(emailVerificationModel.getEmailAddress());
              messageHelper.setFrom(config.getEmailFromAddress());
              messageHelper.setReplyTo(config.getEmailReplyToAddress());
              messageHelper.setSubject(emailSubject);
              Map model = new HashMap();
              model.put("model", emailVerificationModel);
              String text = 
                 VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, 
                 velocityModel, model);
                messageHelper.setText(new String(text.getBytes(), "UTF-8"), true);
                    for(String resourceIdentifier: resources.keySet()) {
                       addInlineResource(messageHelper, 
                       resources.get(resourceIdentifier),
                      resourceIdentifier);
                    }
              }
          };
        LOG.debug("Sending Verification Email to : {}",
                   emailVerificationModel.getEmailAddress());
        this.mailSender.send(preparator);
        return emailVerificationModel;
    }
The velocity templates are just bare-bones and will need to be customised to your needs.

Handling the Verification Request


The controller method passes through to the Verification Token Service
@PermitAll
    @Path("tokens/{token}")
    @POST
    public Response verifyToken(@PathParam("token") String token) {
        verificationTokenService.verify(token);
        return Response.ok().build();
    }

The service method to verify the token

    @Transactional
    public VerificationToken verify(String base64EncodedToken) {
        VerificationToken token = loadToken(base64EncodedToken);
        if (token.isVerified() || token.getUser().isVerified()) {
            throw new AlreadyVerifiedException();
        }
        token.setVerified(true);
        token.getUser().setVerified(true);
        userRepository.save(token.getUser());
        return token;
    }

    private VerificationToken loadToken(String base64EncodedToken) {
        Assert.notNull(base64EncodedToken);
        String rawToken = new String(Base64.decodeBase64(base64EncodedToken));
        VerificationToken token = tokenRepository.findByToken(rawToken);
        if (token == null) {
            throw new TokenNotFoundException();
        }
        if (token.hasExpired()) {
            throw new TokenHasExpiredException();
        }
        return token;
    }

The token is decoded and matched from the repository to an existing user. If that user has not already been verified then they are set to verified.

Testing the API

Before we can test the API the email service has to be configured. The context file containing the MailSender bean is at:

src/main/resources/META-INF/spring/email-template-context.xml

Choose a Mail Sender and insert the values into the mail properties. You can use a gmail or yahoo account for testing or there are several useful bulk mail sending options such as CritSend or MailJet.

<beans profile="production, staging">

    <bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">

      <!-- Email provider here-->

      <property name="host" value=" <insert your Host name here>"/>

      <property name="port" value=" <insert the port here>"/>

      <property name="username" value=" <insert your username here>"/>

      <property name="password" value=" <insert your password>"/>

        <property name="javaMailProperties">

          <props>

           <prop key="mail.debug">false</prop>

           <prop key="mail.smtp.auth">true</prop>

           <prop key="mail.smtp.starttls.enable">true</prop>

          </props>

        </property>

   </bean>

As you can see only the staging and production profiles are set up to send real mail. When running the dev and local profiles the MockJavaMailSender is invoked which simply stores the Mime messages in memory. To test out sending mail we can temporarily add the dev profile:

<beans profile="dev, production, staging">

and remove it from the other beans profile:

<beans profile="local">

Now start the application by executing:

gradle tomcatRun

Create a new user with the following curl statement (substituting in your real email address):

curl -v -H "Content-Type: application/json" -X POST -d '{"user":{"firstName":"Foo","lastName":"Bar","emailAddress":" <your email address>"}, "password":"password"}' http://localhost:8080/java-rest/user

You should then receive an email with the subject:

"Welcome to the Java-REST sample application"

This is configurable in app.properties although that will be covered in a later post.

You can either click on the link in the email and this will invoke a static page in the application that will forward on the token to another API call to verify the token. Alternatively you can copy the token in the link and construct a curl statement to do the same thing.

A sample curl to verify the token (substitute the token in your email):

curl -v -H "Content-Type: application/json"  -X POST -d  localhost http://localhost:8080/java-rest/verify/tokens/NjM1NjBkNTAtNjEwZi00N2I3LTk0MmQtYWMwMjVkY2MwNTc1

You should see this response:

< HTTP/1.1 200 OK

< Server: Apache-Coyote/1.1

< Content-Type: application/json

< Content-Length: 0

< Date: Mon, 14 Jan 2013 08:30:18 GMT

If you execute the curl again you will receive a 409 response with a message saying the token has already been verified:

< HTTP/1.1 409 Conflict

< Server: Apache-Coyote/1.1

< Content-Type: application/json

< Transfer-Encoding: chunked

< Date: Mon, 14 Jan 2013 08:30:58 GMT

{"errorCode":"40905","consumerMessage":"Already verified","applicationMessage":"The token has already been verified"}

What if a user changes their email address? In that case we should resend a verification email and have them verify their email address again. In the user/<id> PUT resource handler that is exactly what happens:

    @RolesAllowed({"authenticated"})
    @Path("{userId}")
    @PUT
    public Response updateUser(@Context SecurityContext sc, @PathParam("userId") 
                 String userId, UpdateUserRequest request) {
        ExternalUser userMakingRequest = (ExternalUser)sc.getUserPrincipal();
        if(!userMakingRequest.getId().equals(userId)) {
            throw new AuthorizationException("User not authorized to 
               modify this profile");
        }
        boolean sendVerificationToken = 
                 StringUtils.hasLength(request.getEmailAddress()) &&
                !request.getEmailAddress().equals(userMakingRequest.getEmailAddress());
        ExternalUser savedUser = userService.saveUser(userId, request);
        if(sendVerificationToken) {
            verificationTokenService.sendEmailVerificationToken(savedUser.getId());
        }
        return Response.ok().build();
    }

This is an access controlled resource which will be covered in a Part six: security and authorization.

The next post will cover Facebook authentication.