Priyanka Phillips
Priyanka Phillips in Tutorials
Fri Jul 19 2019 · 17 min read

Create a REST API [Part 5]: Verify Users with Tokens

In this tutorial, you will create a new API route that is passed a verification token used to confirm a user registration through your API by updating an entry in your PostgreSQL database. This is part 5 of a series of tutorials on building a REST API in Node with ExpressJS, KnexJS, and PostgreSQL.

More From This Series

• Part 5 - Verify Users with Tokens
Create a REST API [Part 5]: Verify Users with Tokens

Download the code for this series

Now that you have sent out an email with a verification token, you need a way to accept that token and in turn verify that a user has access to the email address they registered with on your website. Let's do that.

1. Create a verification route

In your users.js file, add another route that will take in the token that is sent when a user registers through your API. Under your '/register' route and a new post route for '/verify/:token' that will grab the token from the request parameters:

router.post("/verify/:token", (req, res) => {
  const { token } = req.params;
  const errors = {};

Add a database query that checks if the token exists and has not been used before. In which case, return an "Email verifed!" message and update the 'emailverifed' and 'tokenusedbefore' fields to true:

  database
    .returning(["email", "emailverified", "tokenusedbefore"])
    .from("users")
    .where({ token: token, tokenusedbefore: "f" })
    .update({ emailverified: "t", tokenusedbefore: "t" })
    .then(data => {
      if (data.length > 0) {
        res.json(
          "Email verified! Please login to access your account"
        );
      } 

If the above query comes back empty, check the database again to see if the token exists and if 'emailverified' is true. In which case send a message stating 'Email already verified':

      else {
        database
          .select("email", "emailverified", "tokenusedbefore")
          .from("users")
          .where("token", token)
          .then(check => {
            if (check.length > 0) {
              if (check[0].emailverified) {
                errors.alreadyVerified =
                  "Email already verified. Please login to your account.";
                res.status(400).json(errors);
              }

If token is absent there could be two possibilities, the user did not register or the token has expired:

            } else { 
              errors.email_invalid =
                "Email invalid. Please check if you have registered with the 
                correct email address or re-send the verification link to your 
                  email.";
              res.status(400).json(errors);
            }
          })
          .catch(err => {
            errors.db = "Bad request";
            res.status(400).json(errors);
          });
      }
    })
    .catch(err => {
      errors.db = "Bad request";
      res.status(400).json(errors);
    });
});

see the complete code for users.js here

2. See it in action

With your server running, take the token that you received by email in part 4 of this series (everything after 'htts://yourwebsite/v1/users/verify/')

temptemp

and add a new post request in Postman to your '/verify/:token' route. If everything is working you should get the following message back from your API:

temptemp

Hint! Enter in the wrong token and see what message you get back in Postman

3. Add a token expiry time

Creating tokens that never expire and sending them out over the internet is not very good for security. Let's create a tokenExpiry.js file in the utilities folder to fix this:

// simple-api/utilities/tokenExpiry.js

const database = require("../database");

// Function runs every 4 seconds
setInterval(async function checkRegistrationTokenValidity() {
  await database
    .select("id", "createdtime")
    .from("users")
    .then(timeOfTokenCreation => {
      timeOfTokenCreation.map(entryTime => {
        // Convert UTC time to an integer to compare with current time
        let timeInInt = parseInt(entryTime.createdtime);

        // Check if an hour has passed since the token was generated
        if (Date.now() > timeInInt + 60000 * 60) {
          database
            .table("users")
            .where("id", entryTime.id)
            .update({ token: null }) //updates old tokens to null
            .then(res => res)
            .catch(err => err);
        }
      });
    })
    .catch(err => console.log(err));
}, 4000);

tokenExpiry.js contains a function that runs every 4 seconds and checks the following:

  • If an hour has passed after a user has registered through the API.
  • If so, the token issued to the user at the time of registration is deleted from the database.

Include tokenExpiry.js is your server.js file so that it will run on startup:

const tokenExpiry = require("./utilities/tokenExpiry");

6. Create a resend email route

Now that tokens expire after an hour, what happens if a user hasn't verified on time but they still want to use your website? Create a new route called 'resend_email' that your users can use to request a fresh token to verify with. But first add a new validation function to check email addresses on this new route. In your validation directory, create a new file called resend.js:

//simple-api/validation/resend.js

const Validator = require("validator");
const ifEmpty = require("./checkForEmpty");

module.exports = function checkResendField(data) {
  let errors = {};

  data.email = !ifEmpty(data.email) ? data.email : "";

  if (Validator.isEmpty(data.email)) {
    errors.email = "Email is required";
  }
  if (!Validator.isEmail(data.email)) {
    errors.email = "Email address is invalid";
  }

  return {
    errors,
    isValid: ifEmpty(errors)
  };
};

Now import the function at the top of users.js:

// simple-api/api/routes/users.js

const express = require("express");
const router = express.Router();
const bcrypt = require("bcryptjs");
const crypto = require("crypto");
const database = require("../../database");
// Send email utility
const sendEmail = require("../../utilities/sendEmail");
// Validation
const checkRegistrationFields = require("../../validation/register");
// Resend email validaiton
const checkResendField = require("../../validation/resend");

Add a 'resend_email' route under the '/verify' route with crypto.randomBytes again to generate a fresh token:

router.post("/resend_email", (req, res) => {

  const { errors, isValid } = checkResendField(req.body);

  if (!isValid) {
    return res.status(400).json(errors);
  }

  let resendToken;
  crypto.randomBytes(48, (err, buf) => {
    if (err) throw err;
    resendToken = buf
      .toString("base64")
      .replace(/\//g, "")
      .replace(/\+/g, "-");
    return resendToken;
  });

add two database calls within the '/resend_email/ route:

  database
    .table("users")
    .select("*")
    .where({ email: req.body.email })
    .then(data => {
      if (data.length == 0) {
        errors.invalid = "Invalid email address. Please register again!";
        res.status(400).json(errors);
      } else {
        database
          .table("users")
          .returning(["email", "token"])
          .where({ email: data[0].email, emailverified: "false" })
          .update({ token: resendToken, createdtime: Date.now() })
          .then(result => {
            if (result.length) {
              let to = [result[0].email];

              let link =
                "https://yourWebsite/v1/users/verify/" + result[0].token;

              let sub = "Confirm Registration";

              let content =
                "<body><p>Please verify your email.</p> <a href=" +
                link +
                ">Verify email</a></body>";
              sendEmail.Email(to, sub, content);

              res.json("Email re-sent!");
            } else {
              errors.alreadyVerified =
                "Email address has already been verified, please login.";
              res.status(400).json(errors);
            }
          })
          .catch(err => {
            errors.db = "Bad request";
            res.status(400).json(errors);
          });
      }
    })
    .catch(err => {
      errors.db = "Bad request";
      res.status(400).json(errors);
    });
});

Using the email passed to the resend_route, check if the email exists and if email has not been verified. Send the token if so.

If the email address has been registered in the database but is not yet verified an 'Email re-sent!' message is returned (and a new token is sent):

temptemp

Conclusion

If you have been following along with this series since the beginning, your registration flow should now resemble the the chart below:

temptemp

You now have an API which handles:

  • User registration
  • Sending verification emails
  • Verifying tokens and confiming registration
  • Allowing users to resend tokens when the forget them

There is a lot going on there. Let us know in the comments how you are using this API in your projects!

Move on to part 6 of this series where we will create a user login route with JSON Web Token authentication.