Adding Email Verification to an Adonis Web App: A Step-by-Step Guide

Tue Dec 19 2023

AdonisJS is a popular Node.js MVC framework that is inspired by Laravel. It provides a robust set of features and tools for building web applications. One important aspect of any web app is user authentication, and email verification is a crucial component of it. In this article, we will walk through the steps for adding email verification to an AdonisJS web app.

This article assumes you already have authentication and a database setup in your application.

Creating database migrations:

To get started, lets start by adding an email_verified boolean to your user model. We’ll do this by first creating a migration on the user table using node ace make:migration users. In this file, we’ll alter the table to add a boolean column, like so;

import BaseSchema from "@ioc:Adonis/Lucid/Schema";

export default class extends BaseSchema {
  protected tableName = "users";

  public async up() {
    this.schema.alterTable(this.tableName, (table) => {
      table.boolean("email_verified").defaultTo(false);
    });
  }

  public async down() {
    this.schema.dropTable(this.tableName);
  }
}

We’ll also create a new table called “tokens” where we’ll store the verification tokens sent to the user’s email address.

import BaseSchema from "@ioc:Adonis/Lucid/Schema";

export default class extends BaseSchema {
  protected tableName = "tokens";

  public async up() {
    this.schema.createTable(this.tableName, (table) => {
            table.increments("id").unique().primary();
      table.string("token");
      table.string("user_id").references("users.id");
      table.string("type");

      table.timestamp("created_at", { useTz: true }).notNullable();
      table.timestamp("updated_at", { useTz: true }).notNullable();
    });,
  }

  public async down() {
    this.schema.dropTable(this.tableName);
  }
}

Note; in this migration, I have user_id column set to a string, your application may have users.id as an integer type. Additionally, I’ve set the id column to increment in the tokens table, but you may choose to use UUIDs instead.

As we’ve created two new migrations, we’ll need to update their corresponding models. Create a new model for the tokens table using node ace make:model token.

In the Token model file, add the following;

...
@column()
public token: string;

@column()
public user_id: string;

@column()
public type: string;

@belongsTo(() => User, {
  foreignKey: "user_id",
})
public user: BelongsTo<typeof User>;
...

In your User model, add the following;

...
@column()
public email_verified: boolean;

@hasMany(() => Token, {
  localKey: "user_id",
  foreignKey: "id",
})
public tokens: HasMany<typeof Token>;
...

This sets up a One-to-Many relationship between the two tables, so querying a user will allow us to return their tokens and vice versa.

Implementing Email Verification:

Now that we’ve got the database changes out of the way, lets get to the main event; implementing email verification in your Adonis web app. The flow we’ll be implementing goes like this;

  • User signs up
  • Token is generated and saved to DB
  • An email containing the token is sent to the User
  • User clicks the link containing the token
  • Link verifies the email address in the DB

Adonis has an email module, Mailer, which I’ll be using to send emails via Postmark. The setup is straightforward and will allow us to send emails seamlessly through Adonis

User signs up, Token Generation and Emails

Navigate to the function that creates a new user once they’ve signed up. Here we want to generate a new token, save it to the database and then email the user with the token. After the user is created, add the following code:

...
    const token = await Token.create({
      user_id: user.id,
      token: Encryption.encrypt(user.id + user.createdAt),
      type: "email_verification",
    });

    await Mail.send((message) => {
      message
        .from("test@email.com") // Replace with your own email
        .to(user.email)
        .subject("Email verification")
        .htmlView("emails/verification", { token: token.token });
    });
    ...

We’re sending an html email which means our email will contain html and can be used the same as other .edge templates. To generate this view, use node ace make:view emails/verification . In this file, we’ll add a simple anchor tag with the link to our web app’s verification page and the token as a query code.

<a href=https://ouradonisproject.com/verify-email?token=undefined>Verify Email</a>

Verifying an email address

The next step is to add the route we send in the email, verify-email, which will handle verifying the user’s email address.

Inside this route, we’ll add the following logic that will find the token in the database, and update the user’s record before deleting the token from the database.

Route.get("/verify-email", async ({ view, request, response }) => {
  const token = request.input("token");

  if (!token) {
    return response.redirect("/");
  }

  const existingToken = await Token.query()
    .preload("user")
    .where("token", token)
    .first();

  if (!existingToken) {
    return response.redirect("/");
  }

  // Update the user attached to the token and save.
  existingToken.user.email_verified = true;
  existingToken.user.save();

  // Delete the token
  await existingToken.delete();

  return view.render("emails/verified");
});

For this, I’ve used a simple view called emails/verified which shows a message once the user is verified.

Email address verified

Testing the Email Verification Feature:

The final part is to test that the system works. To do this, sign up as a new user and check your inbox for an email titled “Email verification”. Click the link inside and check that the page loads.

Additionally, check that the user’s email_verified column now has a 1 in it instead of a 0, signifying that their email is now verified.

Conclusion:

In conclusion, adding email verification to an Adonis web app is a straightforward process that enhances the security of your application. By following the steps outlined in this article, you can easily implement email verification in your Adonis app and provide a better user experience for your users.

Thanks for reading and happy coding! 🙂

Table of Contents