Account Verification

Once a user signs up for service on our site we want to make sure we have some type of verification system in place to help ensure they are a real user. Quite often this is done by sending out an email with an account verification link which allows a prospective user to finalize their account creation.

In this article we will go over how to build all the pieces necessary to bring this functionality together. This includes:

  • email integration
  • generate a registration token
  • create an endpoint to verify a user

Email Integration

First we need to update our project pom file to include support for email. This can easily be done with the following:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

Next, we will need to update our application.properties file to configure the emails we are going to send.

spring.mail.host=<outgoing email server>
spring.mail.port=<port number>
spring.mail.username=<username>
spring.mail.password=<password>
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.socketFactory.port = 465
spring.mail.properties.mail.smtp.socketFactory.class = javax.net.ssl.SSLSocketFactory
spring.mail.properties.mail.smtp.connectiontimeout=5000
spring.mail.properties.mail.smtp.timeout=5000
spring.mail.properties.mail.smtp.writetimeout=5000

The port used is very important as we want to send our emails securely. In the setup above we are using SSL. There are other options for configuring the email server however, we want to focus on best practices so we are only going to use SSL in this tutorial. For a good in depth description of the different options take a moment and read the article SSL vs TLS vs STARTTLS.

With Spring constructor injection we can get a hold of a JavaMailSender instance that we can easily use for sending an email. Here we are sending a test email to the specified recipient with the subject of ‘Welcome!’ and some test content.

@Component
private class Emailer {
    private JavaMailSender mail;

    public Emailer(JavaMailSender mail) {
        this.mail = mail;
    }

    public void sendTestEmail(String toAddress) {
        SimpleMailMessage msg = new SimpleMailMessage();
        msg.setTo(toAddress);
        msg.setFrom("spring-test@99milestoempty.com");
        msg.setSubject("Welcome!");
        msg.setText("Hello from 99milestoempty!");
        // Send it!
        mail.send(msg);
    }
}

Now that we’re able to send emails, it’s time to generate a token that can be used to verify a user that has recently registered on our site.

Generate a Registration Token

Registration tokens for account verification are straight forward. They are strings we will use to create a URL and email to our users when they register on our site. Strings must be unique and long enough so that they cannot be easily guessed. For even further security, we can add a time limit to how long they are valid. This particular feature will come in handy later when we add the ability to reset passwords.

Here we have the first iteration of the code for our registration token.

@Entity
@Table
public class RegistrationToken {
    /** How long our generated token will be */
    private static final int length = 64;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String token;
    private String username;
    private LocalDateTime created;
    private LocalDateTime consumed;
    private RegistrationToken() {}

    public RegistrationToken(String username) {
        this.username = username;
        this.token = RandomStringUtils.random(length, true, true);
        this.created = LocalDateTime.now();
    }

    public Long getId() { return id; }
    public String getToken() { return token; }
    public String getUsername() { return username; }
    public LocalDateTime getCreated() { return created; }
    public LocalDateTime getConsumed() { return consumed; }
    public void setConsumed(LocalDateTime consumed) { this.consumed = consumed; }
}

Our class will generate a random 64 character long string that includes letters and numbers that can be used to construct a verification URL for a specific user. We also use this class to keep track of when the token in generated in case we want to expire it. Once the token is used, we can mark the time it was consumed.

An email can now be generated that includes our registration token as part of the URL. In the next section we’ll create the service endpoint that users will visit when confirming their account.

Create an endpoint to verify a user

To have our service verify a user we need to define an endpoint that matches against a registration token. Doing this in Spring is quite easy by using a PathVariable. When a user clicks on the URL in their email it will bring them to this endpoint where we will verify the registration token and if valid, enable their user account.

Below is an endpoint definition that handles verifying the token and marks the user account as enabled. In the unhappy path, we send back an error identifying why the request failed.

@GetMapping(value = "/verifyRegistration/{registrationToken}")
public ResponseEntity<?> verifyRegistration(@PathVariable String registrationToken) {
    // Check if the registration key is valid
    RegistrationToken result = registrationKeyRepository.findFirstByTokenOrderByCreatedDesc(registrationToken);
    // If we find a registration key and it hasn't been used already.
    if (result != null && result.getConsumed() == null) {
        String username = result.getUsername();
        CustomUserDetails userDetails = (CustomUserDetails) userDetailsManager.loadUserByUsername(username);
        // Check if the user has already been activated.
        if (userDetails.isEnabled()) {
            // This user is already enabled.
            // Should we consume this registration key, leave it or mark it as invalid?

            return ResponseEntity.badRequest().body("User is already verified.");
        } else {
            // Enable this user.
            userDetails.getUser().setEnabled(true);
            userDetailsManager.updateUser(userDetails);

            // Consume the registration key.
            result.setConsumed(LocalDateTime.now());
            registrationKeyRepository.save(result);

            return ResponseEntity.ok().body("User is now verified.");
        }
    }
    // No registration key was found for the request or is was already used.
    return ResponseEntity.badRequest().build();
}

Our system is starting to take shape! Stay tuned for the next installment where we will talk about multi-factor authentication.

Also, if you are looking for hosting please consider Dreamhost. If you’d like to sign up with them and feel inclined to throw me some credit, please use my referral link https://www.dreamhost.com/r.cgi?571777.

Leave a Reply

Your email address will not be published. Required fields are marked *