How to setup email verification in Feathers.js
Tags: typescript, node, feathers, webdev
Published: 2021-09-02 06:07:41

Surely you want to send a verification email to the user when they have registered in your app. In this post, you will learn how to do it using [Feathers.js](http://feathersjs.com)

In this post we will take for granted, that you have knowledges about the framework. If not, visit the following link https://docs.feathersjs.com/guides for more information.

**Let's go!**

## 1. First steps.
We create a folder.
`mkdir feathers-email-verification`

Enter you folder
`cd feathers-email-verification`

We create an app with **feathers-cli**
`feathers generate app`

Select the following options
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/670xbfpjhhmtfapi6aih.png">

> Note: For use **feathers-cli** is necessary have it installed. visit this link https://docs.feathersjs.com/guides/basics/generator.html#generating-the-application

Now to verify that installation has been successful. We run the MongoDB database server, we run our development server with the command `npm run start`

Now if we visit http://localhost:3030 we will see the following
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jwazlswitm1xj8boosb0.png" width="100%">

Perfect, now we are going to register a user and then login.

**Create a new user**

<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wvgkqwokx53avdpvnyh8.png">

If the user has been created successfully, we will get the following response.

<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztcya2ahw8qyv5mhkhl4.png">

**Authenticate user**

<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ailknse9zabbn464vtv.png">

If the credentials have been correct and everything went well, we will get the following answer.

<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kh6be4silfjezmunlb9b.png">

Wonderful, everything has been correct. It is time to implement the email delivery system.

## How it all works.
Before entering with the configuration, it is necessary to understand its operation. What we are going to create is a flow to have the user verify their email address. This goes as follows:

1. The user create an account in the application of feathers.
2. The server add field **isVerified** to the object user in the database and set to `false`
3. The server create a **token** of verification for the user.
4. The use gets an email that contains a client link with the token as a parameter.
5. The user clicks the link and, upon visiting the client, this token is sent back to the server.
6. The server sets the field **isVerified** on the user object to `true`
7. Everything is over.

Excellent, it's all you need to know. Now it's time to get to work.

## Setup the email service.
Well, let's create a service that allows us to send email. 

But first you need to download the package. So we install [nodemailer](https://nodemailer.com/about).
`npm i nodemailer`

We install the types.
`npm i @types/nodemailer`

What we will do is create a mails folder, this folder will contain 3 files: 

> - `mails.class.ts`: You will have the logic to send emails, here we initialize **nodemailer**
> - `mails.hooks.ts`: This file will contain all of our hooks. If you don't know what hooks are in feathers, visit the following link: [Feathers hooks](https://docs.feathersjs.com/guides/basics/hooks.html)
> - `mails.service.ts`: Here we register the service, what will its path be, add the hooks, etc.

Well, we create to `mails.class.ts`

```typescript
src > services > mails > mails.class.ts

import { Application } from "@feathersjs/express";
import { ServiceMethods } from "@feathersjs/feathers";
import { createTransport, Transporter, SendMailOptions } from "nodemailer";

export class Mails implements Partial<ServiceMethods<SendMailOptions>> {
    private transporter: Transporter;

    constructor(app: Application) {
        // We initialize the transporter.
        this.transporter = createTransport(app.get("mailer"));
    }

    /**
     * We send the email.
     * @param data 
     * @returns 
     */
    async create(data: Partial<SendMailOptions>): Promise<any> {
        return await this.transporter.sendMail(data);
    }
}

```

Now we create the `mails.hooks.ts` file

```typescript
src > services > mails > mails.hooks.ts

import { HooksObject } from "@feathersjs/feathers";

const hooks: HooksObject = {
    before: {},
    after: {},
    error: {}
};

export default hooks;
```

Now we create the `mails.service.ts` file

```typescript
src > services > mails > mails.service.ts

import { Application } from "@feathersjs/express";
import { Service } from "@feathersjs/feathers";
import { SendMailOptions } from "nodemailer";

import hooks from "./mails.hooks";

import { Mails } from "./mails.class";

export default function (app: Application): void {
    // Register service.
    app.use("mails", new Mails(app));

    // Get mail service.
    const service: Service<SendMailOptions> = app.service("mails");

    // Register hooks in the service.
    service.hooks(hooks);
}
```

Great! Now that everything is almost done, it is necessary to import this service into the global services of the application so that it is available throughout the app.

```typescript
src > services > index.ts

import { Application } from '../declarations';

import users from './users/users.service';

// We import the mail service.
import mails from "./mails/mails.service";

export default function (app: Application): void {
  // We expose the service to the rest of the application.
  app.configure(mails);
  app.configure(users);
}

```

As you may have already noticed, we have an error, this is because in our environment variables file `defaul.json` it does not find the `mailer` property, what we have to do is add it and be all set.

<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9oas1fhvt6j4f1831ino.png">

> Note: By the way, do not expose the `user` and `pass` since this is confidential content, I did it for didactic reasons. But in the same way I have already reset the credentials.

We start the development server again with `npm run dev` and error will be gone.

To the check that everything is correct, we will send this request.

<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hlnmy60ff1j7despm39t.png">

If everything went well, we should see this in mailtrap.

<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gv7ejl6h4iigxjjc5x5c.png">

> If you wonder what service he is using to test the sending of emails, that is nothing more and nothing less than [mailtrap](https://mailtrap.io/)

Obviously we do not want our mailer to be misused for spam or something, so after testing we are going to close it off by adding a before hook on the all mailer routes. For this we install the [feathers-hooks-common](https://hooks-common.feathersjs.com)

```typescript
src > services > mails > mails.hooks.ts

import { HooksObject } from "@feathersjs/feathers";
import { disallow } from "feathers-hooks-common";

const hooks: HooksObject = {
    before: {
        // Reject all requests that come from outside.
        all: [disallow("external")]
    },
    after: {},
    error: {}
};

export default hooks;
```

To check if it rejects the requests, we send email again, as we did in the previous step, and this should give us an error. 

Wonderful, we have finished with the configuration of sending emails.

## Setting up the Feathers-Authentication-Managment module.

Now we are going to set up the feathers-authentication-management module. First let’s install it.

`npm i feathers-authentication-management-ts`

Once installed, we are going to create a folder with the name **authmanagement** inside the **services** folder. This folder will have the following structure.

<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbe080ibazg5yx8y2s0q.png">

**index.strategies.ts**
This file will contains all strategies. The strategies that will be present are `verificationEmail` and `verifiedEmail`. Copy the following content.

```typescript
src > services > authmanagement > strategies > index.strategies.ts

import { User, Types } from "feathers-authentication-management-ts";
import { SendMailOptions } from "nodemailer";

export interface MailOptions extends Partial<SendMailOptions> {
    user: User;
    token?: string;
    domain: string;
}

export interface ParamsLink {
    type?: string;
    token?: string;
    domain: string;
}

export type StrategiesAuthManagement = Record<
    Types,
    (options: MailOptions) => MailOptions
>;

export function generateLink(data: ParamsLink): string {
    const { domain, type, token } = data;
    return `${ domain }/${ type }?token=${ token }`;
}

export function verificationEmail(options: MailOptions): MailOptions {
    const { token, domain } = options;
    const link: string = generateLink({ token, domain, type: "verifyEmail" });

    return {
        ...options,
        subject: "Email Verification",
        text: "Feathers welcomes you, check your email to access our services 📧",
        html: `
            <h1>Thanks for registering 🥰</h1>
            <p>Verify your email and everything will be ready.</p>
            <a href="${ link }">Verify your email</a>
        `
    };
}

export function confirmationEmail(options: MailOptions): MailOptions {
    const html: string = `
        <h1>Your email has been verified</h1>
        <p>Great, now that your account is verified. It is time to get down to work.</p>
    `;

    return {
        ...options,
        subject: "Verified Email",
        text: "Congratulations! Your email has been verified 🏆",
        html
    };
}

export const strategies: Partial<StrategiesAuthManagement> = {
    resendVerifySignup: verificationEmail,
    verifySignup: confirmationEmail
}

```

**authmanagement.controller.ts**
This file will contain all the logic of how to obtain the strategies according to their types, it will be responsible for interacting with the email service. That we had previously configured. Copy the following content.

```typescript
src > services > authmanagement > authmanagement.controller.ts

import { SendMailOptions } from "nodemailer";
import { Application } from "@feathersjs/express";
import { MethodNotAllowed } from "@feathersjs/errors";
import { Service } from "@feathersjs/feathers";
import { Options, Types, User } from "feathers-authentication-management-ts";

import { strategies, MailOptions } from "./strategies/index.strategies";

export default function (app: Application): Partial<Options> {
    return {
        notifier(types: Types, user: User): void {
            // Get strategy by types.
            const strategy = strategies[types];

            // Check if the strategy exists.
            if (typeof strategy !== "function") throw new MethodNotAllowed({
                name: "StrategyNotAllowed",
                message: `The <${types}> strategy has not been implemented`
            });

            // Get email service.
            const email: Service<SendMailOptions> = app.service("mails");

            // Set payload.
            const payload: MailOptions = strategy({
                from: app.get("email_domain"),
                to: user.email,
                token: user.verifyToken,
                domain: app.get("domain"),
                user
            });

            // Dispatch email.
            email.create(payload)
                .then(() => console.log("Sent email successfully"))
                .catch(console.error)
        }
    };
}

```

> Note: If the url of your users service is different from `users` for example `/api/users`, it is important that you add the url of the users service that you assigned, since by default it will look in `users`

```typescript
src > services > authmanagement > authmanagement.controller.ts

export default function (): Partial<Options> {
    return {
        // Name of the user service.
        service: "<Your name user service>",
        // Notifier.
        notifier(type: Types, user: User) {}
    }
}
```

**authmanagement.hooks.ts**
It contains all the hooks of the service. Copy the following content.

```typescript
src > services > authmanagement > authmanagement.hooks.ts

import { HooksObject } from "@feathersjs/feathers";

const hooks: HooksObject = {
    before: {},
    after: {},
    error: {}
}

export default hooks;

```

**authmanagement.service.ts**
You will be responsible for registering the service. Copy the following content.

```typescript
src > services > authmanagement > authmanagement.service.ts

import { Application } from "@feathersjs/express";
import { Service } from "@feathersjs/feathers";
import authmanagement from "feathers-authentication-management-ts";

import hooks from "./authmanagement.hooks";

import controller from "./authmanagement.controller";

export default function (app: Application): void {
    // Initialize service.
    app.configure(authmanagement(controller(app)));

    // Get service.
    const service: Service<any> = app.service("authManagement");

    // Add hooks.
    service.hooks(hooks);
}
```

Now add the **authmanagement** service to the global services.

```typescript
src > services > index.ts

import authmanagement from "./authmanagement/authmanagement.service";

export default function (app: Application): void {
  // Configure my auth management.
  app.configure(authmanagement);
  // More services...
}

```

> Note: If you use any ORMs like Mongoose or Sequelize you need to add the verification fields manually to the user model. These need to be the following fields.

```typescript

const schema = new Schema({
    // More properties...
    isVerified: { type: Boolean },
    verifyToken: { type: String },
    verifyExpires: { type: Date },
    verifyChanges: { type: Object },
    resetToken: { type: String },
    resetExpires: { type: Date }
});
```

Finally, we need to add two after create hooks to our user model. One to call our notifier function and one to remove the verification again. That looks like this.

```typescript
src > services > users > users.hooks.ts

import * as feathersAuthentication from '@feathersjs/authentication';
import * as local from '@feathersjs/authentication-local';
import { Application } from '@feathersjs/express';
import { HooksObject } from '@feathersjs/feathers';
import { BadRequest } from "@feathersjs/errors";
import authmanagement from "feathers-authentication-management-ts";

import notifier from "../authmanagement/authmanagement.controller";

const { authenticate } = feathersAuthentication.hooks;
const { hashPassword, protect } = local.hooks;

const hooks: HooksObject = {
  before: {
    all: [],
    find: [ authenticate('jwt') ],
    get: [ authenticate('jwt') ],
    create: [
      hashPassword('password'),
      // Sets values to some properties of the users model.
      authmanagement.hooks.addVerification()
    ],
    update: [ hashPassword('password'),  authenticate('jwt') ],
    patch: [ hashPassword('password'),  authenticate('jwt') ],
    remove: [ authenticate('jwt') ]
  },

  after: {
    all: [ protect('password') ],
    create: [
      ({ app, result }) => {
        const sender = notifier(app as Application);

        if (typeof sender.notifier !== "function") throw new BadRequest({
          name: "EmailNotSupported",
          message: "Sending emails not supported"
        });

        sender.notifier("resendVerifySignup", result);
      },
      // Protects sensitive properties before they are shipped to the customer.
      authmanagement.hooks.removeVerification()
    ]
  },
  error: {}
};

export default hooks;
```

## Securing the application.
Now that the app works there is only one step to complete and that is adding some security to the users service. Since we have a nice authentication flow running we don’t want any users to meddle with the user service directly anymore. For this we create two before hooks. One on the update method and one on the patch method. With the one on the update method we are going to disallow this method in its entirety. After all, we wouldn’t want someone to be able to replace our carefully verified user by a new one. The one on the patch method we want to restrict the user from touching any of the authentication field methods directly. To do this we update the user before hooks to:

```typescript
src > services > users > users.hooks.ts

import { HooksObject } from '@feathersjs/feathers';
import feathersCommon from "feathers-hooks-common";

const hooks: HooksObject = {
  before: {
    update: [
      feathersCommon.disallow("external")
    ],
    patch: [
      feathersCommon.iff(
        feathersCommon.isProvider('external'),
        feathersCommon.preventChanges(true,
          'email',
          'isVerified',
          'verifyToken',
          'verifyShortToken',
          'verifyExpires',
          'verifyChanges',
          'resetToken',
          'resetShortToken',
          'resetExpires'
        )
      )
    ]
  }
};

export default hooks;

```

This is the final result.

```typescript
src > services > users > users.hooks.ts

import * as feathersAuthentication from '@feathersjs/authentication';
import * as local from '@feathersjs/authentication-local';
import { Application } from '@feathersjs/express';
import { HooksObject } from '@feathersjs/feathers';
import { BadRequest } from "@feathersjs/errors";
import authmanagement from "feathers-authentication-management-ts";
import feathersCommon from "feathers-hooks-common";

import notifier from "../authmanagement/authmanagement.controller";

const { authenticate } = feathersAuthentication.hooks;
const { hashPassword, protect } = local.hooks;

const hooks: HooksObject = {
  before: {
    all: [],
    find: [ authenticate('jwt') ],
    get: [ authenticate('jwt') ],
    create: [
      hashPassword('password'),
      // Sets values to some properties of the users model.
      authmanagement.hooks.addVerification()
    ],
    update: [
      hashPassword('password'),
      authenticate('jwt'),
      feathersCommon.disallow("external")
    ],
    patch: [
      feathersCommon.iff(
        feathersCommon.isProvider('external'),
        feathersCommon.preventChanges(true,
          'email',
          'isVerified',
          'verifyToken',
          'verifyShortToken',
          'verifyExpires',
          'verifyChanges',
          'resetToken',
          'resetShortToken',
          'resetExpires'
        )
      ),
      hashPassword('password'),
      authenticate('jwt')
    ],
    remove: [ authenticate('jwt') ]
  },

  after: {
    all: [ protect('password') ],
    create: [
      ({ app, result }) => {
        const sender = notifier(app as Application);

        if (typeof sender.notifier !== "function") throw new BadRequest({
          name: "EmailNotSupported",
          message: "Sending emails not supported"
        });

        sender.notifier("resendVerifySignup", result);
      },
      // Protects sensitive properties before they are shipped to the customer.
      authmanagement.hooks.removeVerification()
    ]
  },
  error: {}
};

export default hooks;

```

To check that everything is fine, it is time to register a user.

<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wvgkqwokx53avdpvnyh8.png">

If we go to mailtrap we will look at something like this.

<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qq58kavevfljdacda5yh.png">

Excellent, we are done with the server configuration. In another post, we will build the client part, using [Vuejs](https://vuejs.org) and [Angular](https://angular.io).

I leave you the project repository: https://github.com/IvanZM123/feathers-email-verification

### Follow me on social networks.

- 🎉 Twitter: https://twitter.com/ToSatn2
- 💡 Github: https://github.com/IvanZM123