paint-brush
Setting up email verification in FeathersJSby@gelens.imre
14,291 reads
14,291 reads

Setting up email verification in FeathersJS

by Imre GelensFebruary 14th, 2018
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

This article is an attempt to write down how to get verification emails working on the new version of the FeathersJS framework. The goal of this tutorial is to have a clean feathers app that is able to handle user account creation requests via REST, send the user a validation link and handle the clicking of that link. This article will assume that you already have some knowledge of how to use the core of the feathers framework and general web development practices. We need to create a mailer service to send emails to our users.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Setting up email verification in FeathersJS
Imre Gelens HackerNoon profile picture

Over the past years of web development I have stumbled upon the FeathersJS project and have really loved it so far. It offers a lot of functionality out of the box like websockets and authentication which makes it a great alternative to real-time backends like Firebase at a fraction of the cost. There are very little node frameworks that do so much, so well with so little configuration and the only thing I see wrong with it is that it isn’t more widely used, so let me start off with why you should use FeathersJS as your API backend framework.

Scaffolding your application with feathers-cli gives you websockets out of the box with a single line of code.Add authentication with a second line of code including all the public’s favorites like Facebook, Google and GitHub.A third line of code connects to almost every single database on the planet. For example MongoDB or all SQL databases.Killer integration with Vue and React for real-time updates to your frontend finishes the deal.

With that sales pitch out of the way, wanting to move from prototyping to an actual production scenario, I recently had the requirement to start sending verification emails. There seems to be a module for that named feathers-authentication-management. Going through the documentation for this library was challenging as there is a lot to process at once. There is a tutorial and a repo from Jon Paul Miles, but it seems based on an older version of feathers and has a lot of external dependencies like Pug, Lodash, Mongoose etc. This article is an attempt to write down how to get verification emails working based on the newest version of FeathersJS v3 and without any other dependencies.

The goal of this tutorial is to have a clean feathers app that is able to handle user account creation requests via REST, send the user a validation link and handle the clicking of that link in the simplest way possible. We will implement this one action leaving other actions like password reset, or account changes, for you to implement.

This tutorial will assume that you already have some knowledge of how to use the core of the feathers framework and general web development practices.

All code in this article will be available in the repo: https://github.com/ImreC/feathers-verification-emails

How it all works

What we are going to create is a flow to have the user verify their email address. This goes as follows:

The user creates an account on the feathers appThe server adds a field isVerified to the user object in the database and sets it to falseThe server creates a verification token for the userThe user gets sent an email containing a client link with the token as a parameterThe user clicks the link and on visiting the client this token gets sent back to the serverThe server sets the isVerified field on the user object to trueThe user gets all the superpowers from your awesome application

So roughly we need to do the following things to get this to work.

We need to generate a feathers applicationWe need to create something to send emailsWe need to install the authentication-management package to generate the token and handle the extra fields on the user objectWe need to create hooks to get it all to work togetherWe need to code a simple client to handle the clicked linksWe need to secure some parts of the users service to make sure users communicate via the new authentication management route

So let’s get started.

Step 1: Generating a FeathersJS app

To generate our feathers app we will use the feathers-cli package. As a transport we will stick to simple REST because we don’t really need anything else for now. We only need a local authentication strategy and we are going to use NeDB as a database for simplicity. We can generate all this with the following lines of code

npm install feathers-cli -g
feathers generate app
feathers generate authentication

We can now create our test user by sending a post request to the users table. That’s it, we already have a working app with the possibility to create users and perform authentication. This is what makes FeathersJS awesome.

Step 2: Setting up our mailer service

If we are going to send emails to our users we need some way to actually send email to them. Therefore, we need to create a service to send emails from. Unfortunately, at the time of writing this is not possible from feathers-cli. Therefore, we are going to generate a custom service called mailer on the /mailer route.

feathers generate service

This will give us a mailer folder in the services folder which will contain three files, namely mailer.class.js, mailer.hooks.js and mailer.service.js. Since we are not going to use all the methods of this route but only use it for mailing people we can delete the class file.

We then need to install the feathers-mailer and the nodemailer-smtp-transport package.

npm install feathers-mailer nodemailer-smtp-transport --save

I am using Amazon SES to send emails, but any account accepting smtp will do. Jon Paul Miles uses gmail and that also works perfectly fine. To do it with gmail check out his article. Update the mailer.service.js file to look like this.

Then all configuration is done and you can test your new /mailer route by sending a POST request to /mailer with this as body:

// Initializes the `/mailer` service on path `/mailer`
const hooks = require('./mailer.hooks');
const Mailer = require('feathers-mailer');
const smtpTransport = require('nodemailer-smtp-transport');

module.exports = function (app) {
  app.use('/mailer', Mailer(smtpTransport({
    host: 'email-smtp.us-east-1.amazonaws.com',
    secure: true,
    auth: {
      user: process.env.SMTP_USER,
      pass: process.env.SMTP_PASS
    }
  })));
  
  const service = app.service('mailer');
  service.hooks(hooks);
};

Github Gist

NOTE: both “from” and “to ” emails need to be verified in Amazon SES for this to work. Check out SES sandbox mode for more details.

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 package.

npm install feathers-hooks-common --save

And add the following code to mailers.hooks.js.

const { disallow } = require(‘feathers-hooks-common’);
module.exports = {
 before: {
 all: [
 disallow(‘external’)
 ],
...

You can test this by re-sending you POST request to see that it now fails, making the mailer for your use only.

Now that we have a simple service that can send email it is time to go to the next step. Setting up authentication management.

Step 3: Setting up the feathers-authentication-management module

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

npm install feathers-authentication-management --save

Then we are going to generate a custom service with

feathers generate service
named
authmanagement
. We can leave the authentication for now because we are going to do something with that manually later. Also, we can delete the class file from our service again.

Then we are going to create a notifier.js file in the /authmanagement folder. This file consists of three parts.

  • The getLink function which generates our token url. This can either have a verify token or a reset token included. For now, we are only using the verify token.
  • The sendEmail function which calls our /mailer service internally to send the email.
  • The notifier function which, based on the action type, decides what email to send where. We are now only using the verification part but this can also be used to code the other actions. Also, we will only be sending the plain link to the email. If you want to use html templates or some preprocessor to generate nicer looking emails, you need to make sure they are inserted as a value in the html key in the email object.

s

module.exports = function(app) {

  function getLink(type, hash) {
    const url = 'http://localhost:3030/' + type + '?token=' + hash
    return url
  }

  function sendEmail(email) {
    return app.service('mailer').create(email).then(function (result) {
      console.log('Sent email', result)
    }).catch(err => {
      console.log('Error sending email', err)
    })
  }

  return {
    notifier: function(type, user, notifierOptions) {
      let tokenLink
      let email
      switch (type) {
        case 'resendVerifySignup': //sending the user the verification email
          tokenLink = getLink('verify', user.verifyToken)
          email = {
             from: process.env.FROM_EMAIL,
             to: user.email,
             subject: 'Verify Signup',
             html: tokenLink
          }
          return sendEmail(email)
          break

        case 'verifySignup': // confirming verification
          tokenLink = getLink('verify', user.verifyToken)
          email = {
             from: process.env.FROM_EMAIL,
             to: user.email,
             subject: 'Confirm Signup',
             html: 'Thanks for verifying your email'
          }
          return sendEmail(email)
          break

        case 'sendResetPwd':
          tokenLink = getLink('reset', user.resetToken)
          email = {}
          return sendEmail(email)
          break

        case 'resetPwd':
          tokenLink = getLink('reset', user.resetToken)
          email = {}
          return sendEmail(email)
          break

        case 'passwordChange':
          email = {}
          return sendEmail(email)
          break

        case 'identityChange':
          tokenLink = getLink('verifyChanges', user.verifyToken)
          email = {}
          return sendEmail(email)
          break

        default:
          break
      }
    }
  }
}

Github Gist

Now all we need to do is set up our authmanagement service to use this notifier function. That looks something like this:

// Initializes the `authmanagement` service on path `/authmanagement`
const authManagement = require('feathers-authentication-management');
const hooks = require('./authmanagement.hooks');
const notifier = require('./notifier');

module.exports = function (app) {

  // Initialize our service with any options it requires
  app.configure(authManagement(notifier(app)));

  // Get our initialized service so that we can register hooks and filters
  const service = app.service('authManagement');

  service.hooks(hooks);
};

Github Gist

That’s it, we are now ready to set up our hooks to tie it all together.

Step 4: Setting up authentication management hooks

Now we are ready to set up some hooks to actually get our service to work. For this we need to adapt the

users.hooks.js
file. We need to do a couple of things here.

  • Import the verification hooks from feathers authentication management by adding this line to the top:
    const verifyHooks = require(‘feathers-authentication-management’).hooks;
  • Import our notifier by adding this line:
    const accountService = require(‘../authmanagement/notifier’);
  • Then add
    verifyHooks.addVerification()
    to the before create hook to add verification to our user object. This needs to be after the
    hashPassword()
    hook. What this code does is that it adds some extra fields to our user objects and generates a token.
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.
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:
after: {
    create: [
      context => {
        accountService(context.app).notifier('resendVerifySignup', context.result)
      },
      verifyHooks.removeVerification()
    ]

Giving us the total result:

const { authenticate } = require('@feathersjs/authentication').hooks;
const verifyHooks = require('feathers-authentication-management').hooks;
const accountService = require('../authmanagement/notifier');

const {
  hashPassword, protect
} = require('@feathersjs/authentication-local').hooks;

module.exports = {
  before: {
    all: [],
    find: [ authenticate('jwt') ],
    get: [ authenticate('jwt') ],
    create: [
      hashPassword(),
      verifyHooks.addVerification()
    ],
    update: [ hashPassword(),  authenticate('jwt') ],
    patch: [ hashPassword(),  authenticate('jwt') ],
    remove: [ authenticate('jwt') ]
  },

  after: {
    all: [
      // Make sure the password field is never sent to the client
      // Always must be the last hook
      protect('password')
    ],
    find: [],
    get: [],
    create: [
      context => {
        accountService(context.app).notifier('resendVerifySignup', context.result)
      },
      verifyHooks.removeVerification()
    ],
    update: [],
    patch: [],
    remove: []
  },

  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
};

Github Gist

Now it’s time for the big test. If you finished all this and set up your mailer correctly, you can send a POST request to the user service to create an account. On doing this, the email should be sent to your email address with a verification link inside. If that works, well done! Now all we need to do is create a client page to handle the click.

Step 5: Verifying the email link

For simplicity we will create a basic html page with a XMLHttpRequest() script to handle the verification. Obviously there are better way to handle this with feathers-client and your favorite frontend library. However, that is out of scope of this article. Following the structure of our verification link we will create a new folder in the /public folder of our app called “verify”. Here we will put a new index.html file. All this needs to do is to send a POST request to our /authmanagement service with the following JSON object.

{
    “action”:”verifySignupLong”,
    ”value”: YOUR_TOKEN
}

So in the end all we need to do is create a script that takes the token parameter from the URL and posts this to our endpoint. For this I have created a sample page which looks like this.

<html>
<body>
  <p id = "info">
    Sending token
  </p>
  <script>
    var url = new URL(window.location.href);
    var token = url.searchParams.get("token");
    var obj = {
      action: 'verifySignupLong',
      value: token
    }
    console.log(JSON.stringify(obj))
    console.log(obj)
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 201) {
           // Typical action to be performed when the document is ready:
           document.getElementById("info").innerHTML = 'Verification Successful for action <b>' + obj.action + '</b> and token <b>' + obj.value + '</b>';
        } else {
          document.getElementById("info").innerHTML = xhttp.response
        }
    };
    xhttp.open('POST', 'http://localhost:3030/authmanagement' , true);
    xhttp.setRequestHeader('Content-Type', 'application/json');
    xhttp.send(JSON.stringify(obj));
  </script>
</body>
</html>

Github Gist

If you have implemented this, the full flow should work. You can test this by clicking on the link you sent yourself earlier and checking that the page shows a successful token sent. This should trigger a second email thanking you for verification.

Step 6: 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:

before: {
    all: [],
    find: [ authenticate('jwt') ],
    get: [ authenticate('jwt') ],
    create: [
      hashPassword(),
      verifyHooks.addVerification()
    ],
    update: [
      commonHooks.disallow('external')
    ],
    patch: [
      commonHooks.iff(
        commonHooks.isProvider('external'),
          commonHooks.preventChanges(
            'email',
            'isVerified',
            'verifyToken',
            'verifyShortToken',
            'verifyExpires',
            'verifyChanges',
            'resetToken',
            'resetShortToken',
            'resetExpires'
          ),
          hashPassword(),
          authenticate('jwt')
        )
    ],
    remove: [ authenticate('jwt') ]
  }

Github Gist

NOTE: authentication-management has password hashing built in. To prevent double hashing our password we are only hashing it for external calls.

Next Steps

There are a lot more things to set up after this and a lot more optimizations to make. You can start by adding fancy email templates instead of the link. Another possibility would be to replace the email transport by something else, for example a short verification token via SMS. Or start adding code for any of the other actions that are covered by feathers-authentication-management. To help you on that please refer to:

The article by Jon Paul Miles https://blog.feathersjs.com/how-to-setup-email-verification-in-feathersjs-72ce9882e744. This covers the rest of the actions and gives more info on how to set up the rest.

The (outdated) documentation https://auk.docs.feathersjs.com/api/authentication/local-management.html.

Thanks for reading and happy coding!