Streamlining Email Verification: A Step-by-Step Guide with Spring Boot and Angular
Tags: email, springboot, mongodb, restapi
Published: 2024-01-02 00:13:26

Hello readers, In this article you will learn a complete email verification process. We will implement this using Java, Spring Boot, and Mongo DB. You find the completed code at the end. Also, the video version is below.

{% embed https://youtu.be/qdZTwMVFvP0 %}  

Let us split this process into two parts.
1. Generating email verification link with the token.
2. Rest endpoint to verify the token from the link.

## Workflow

As the first process, we will generate the token, Store it in DB, and send an email with the confirmation link. This will be included after we persist the user details in the registration.

> Additionally you can also create an endpoint that can be utilized when there is an email update.


![Generate Token & Send email Workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gbj3ggc2l5xzz5zebt5p.jpg)

An endpoint that verifies the token by querying the DB and updating the user record that the email has been verified.

![Confirm-email endpoint workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pprh5qq4a1ma4j63t54k.jpg)

## Code Implementation

Let's create an entity that stores the token. This can be leveraged for future use when there is a business requirement for token expiration and many more.


```
@Data
@Document
public class EmailConfirmationToken {
    @Id
    private String id;
    @Indexed
    private String token;
    @CreatedDate
    @ReadOnlyProperty
    private LocalDateTime timeStamp;
    @DBRef
    private User user;
}
```

Below is the repository that will interact with DB.

```
@Repository
public interface EmailConfirmationTokenRepository extends MongoRepository<EmailConfirmationToken, String> {
    EmailConfirmationToken findByToken(String token);
}
```
Now repository is ready, Let's create a service that generates the token,

We will make use of "KeyGenerators" from spring security to generate a random 15-byte token.

```
private static final BytesKeyGenerator DEFAULT_TOKEN_GENERATOR = KeyGenerators.secureRandom(15);
    private static final Charset US_ASCII = Charset.forName("US-ASCII");
```

Now use the DEFAULT_TOKEN_GENERATOR to generate the token and add it to the entity to store it using the repository we created above.

```
String tokenValue = new String(Base64.encodeBase64URLSafe(DEFAULT_TOKEN_GENERATOR.generateKey()), US_ASCII);
        EmailConfirmationToken emailConfirmationToken = new EmailConfirmationToken();
        emailConfirmationToken.setToken(tokenValue);
        emailConfirmationToken.setTimeStamp(LocalDateTime.now());
        emailConfirmationToken.setUser(user);
        emailConfirmationTokenRepository.save(emailConfirmationToken);
```

At this point, the token has been generated and stored in DB. Now let's send out a registration email.

To Configure the email server we are going to use Gmail. Let's move to **Gmail-> Manage Accounts -> Security -> click on 2 Step Verification -> Scroll to the end and click on App Passwords -> Enter the application name and get the password**.

After you get the password add it to the configuration below,

```
spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: YOUR_EMAIL
    password: "YOUR APPPLICATION PASSWORD"
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            required: true
            enable: true
```

Now the email server configuration is ready let's implement the email service as below,

Email service makes use of JavaMailSender API. As we are planning to use HTML messages, I am using the MIME message type and its helper to set the email properties.

> We can also isolate the HTML message to a separate file and include it as a stream instead of an inline string.
 

```
@Service
public class EmailServiceImpl implements EmailService {

    private final JavaMailSender sender;

    public EmailServiceImpl(JavaMailSender sender) {
        this.sender = sender;
    }

    @Override
    public void sendConfirmationEmail(EmailConfirmationToken emailConfirmationToken) throws MessagingException {
        //MIME - HTML message
        MimeMessage message = sender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true);
        helper.setTo(emailConfirmationToken.getUser().getUsername());
        helper.setSubject("Confirm you E-Mail - MFA Application Registration");
        helper.setText("<html>" +
                        "<body>" +
                        "<h2>Dear "+ emailConfirmationToken.getUser().getFirstName() + ",</h2>"
                        + "<br/> We're excited to have you get started. " +
                        "Please click on below link to confirm your account."
                        + "<br/> "  + generateConfirmationLink(emailConfirmationToken.getToken())+"" +
                        "<br/> Regards,<br/>" +
                        "MFA Registration team" +
                        "</body>" +
                        "</html>"
                , true);

        sender.send(message);
    }

    private String generateConfirmationLink(String token){
        return "<a href=http://localhost:8080/confirm-email?token="+token+">Confirm Email</a>";
    }
}
```

Let's add the token generation and send an email after saving user details in your application.

Let's see how it works in action.

Open the UI and register a User with a valid email address,

![Registration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zlogzekc98vd5xryv7ao.png)

Upon registration, let's look at the DB.

![User Document](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5yeyse7c22c3c4grci23.png)
![Email Confirmation Document](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqq8tr5avfx5g7ny7ar7.png)

Here is the email we received.

![Inbox Received confirmation email](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1srkcyvwdew385k9wp8.png)

![Received email body](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zli6rgtollxzyc4gd6hl.png)

The first part is completed.

2. Let's implement the rest endpoint that validates the token,

> If there is a security config that limits the authenticated endpoints then add this endpoint to whitelists. 

```
@GetMapping("/confirm-email")
    public ResponseEntity<?> confirmEmail(@RequestParam("token") String token) throws InvalidTokenException {
        try{
            if(userService.verifyUser(token)){
                return ResponseEntity.ok("Your email has been successfully verified.");
            } else {
                return ResponseEntity.ok("User details not found. Please login and regenerate the confirmation link.");
            }
        } catch (InvalidTokenException e){
            return ResponseEntity.ok("Link expired or token already verified.");
        }
    }
```

Below is the service implementation that will validate the token and update the user record.

Code retries the token from DB with the token received from the endpoint. if there is a token then validate update the user record and delete the token from DB.

```
@Override
    public boolean verifyUser(String token) throws InvalidTokenException {
        EmailConfirmationToken emailConfirmationToken = emailConfirmationTokenRepository.findByToken(token);
        if(Objects.isNull(emailConfirmationToken) || !token.equals(emailConfirmationToken.getToken())){
            throw new InvalidTokenException("Token is not valid");
        }
        User user = emailConfirmationToken.getUser();
        if (Objects.isNull(user)){
            return false;
        }
        user.setAccountVerified(true);
        userRepository.save(user);
        emailConfirmationTokenRepository.delete(emailConfirmationToken);
        return true;
    }
```
Let's check the endpoint by clicking on the email link.


![Email with confirmation link](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iz1a69hv1iq9ira3bknv.png)


![Response from server](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61ewhhaqcz6d4ndodiyb.png)

This endpoint also updated the record and deleted the token,


![Updated account verified](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kanr32e13tcio08rnezt.png)

![Removed the token from DB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fv17xc1vvz8xw6yyd6aq.png)

We have successfully implemented the registration workflow. Great for reading till the end. Check out the GitHub repository & do comment if have any questions, I am happy to answer all.

Backend code
{% embed https://github.com/amaialth/mfaserver %}

Angular UI
{% embed https://github.com/amaialth/MFAApplication %}