Create an email confirmation system with Node JS

Register email addresses to your Node JS application

ยท

7 min read

Introduction

When registering users with their email id, it is essential to check whether the user is the true owner of the email account. This ensures that emails are sent to the appropriate recipient. Such a check can be implemented using two ways. One method is to create a random OTP that is sent by mail and is stored on the backend. Then, the user will be prompted to enter a one time password which will be compared with the value stored.

Another method is to sent a confirmation link via email to the user. Once the user clicks on this special link, the email address is automatically confirmed.

Since the latter requires less effort from the user's perspective, it is more commonly used in web applications. We will also follow the same method to confirm our users.

Installation

The creation of such an application would require several packages.

Firstly, we will install mongoose (a database package), express (a server framework), and googleapis (for sending gmails) with the terminal

npm install mongoose express googleapis

Nodemailer would also be required to send emails

npm install nodemailer

Importing packages

In your app.js file, import the packages using the following code

const mongoose = require("mongoose");
const express = require("express");
const nodemailer = require("nodemailer");
const {google} = require("googleapis");

In the last import, we are destructuring the object from googleapis package to get the google variable.

Setting up our database

mongoose.connect("mongodb://localhost:27017/mainDB", {
    useNewUrlParser: true,
    useUnifiedTopology: true
});

const userSchema = new mongoose.Schema({
    email: String,
    confirmed: Boolean
})

const linkSchema = new mongoose.Schema({
    email: String,
    random_string: String
})

const User = mongoose.model("User", userSchema);
const Link = mongoose.model("Link", linkSchema);

In the above code, the userSchema is used to store email Ids registered. It has a confirmed property used to check whether each email ID is confirmed by the owner.

The linkSchema has records that contain the random string โ€” identical to the string in the confirmation link sent via email. When the user clicks on this link, the string on the link is compared with the corresponding string in the database. If they match, confirmed will be set to true

Setting up our server

const app = express();

app.post("/register", (req, res) => {

})

app.listen(5000, () => console.log("Server running on port 5000"));

Creating a send mail function

To send an email from your Gmail account, you need to get the CLIENT_ID, CLIENT_SECRET, REDIRECT_URI and REFRESH_TOKEN corresponding to your gmail account.

If you do not have these, refer to the following blog on how you can find the data

aryaan.hashnode.dev/how-to-get-credentials-..

Store the values as variables

const CLIENT_ID = YOUR_CLIENT_ID
const CLIENT_SECRET = YOUR_CLIENT_SECRET
const REDIRECT_URI = YOUR_REDIRECT_URI
const REFRESH_TOKEN = YOUR_REFRESH_TOKEN

Ensure that the information is in your env file for security purposes

Next, to create an oAuth2Client, we must use the following code

const oAuth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI);
oAuth2Client.setCredentials({ refresh_token: REFRESH_TOKEN});

Now let us create the send mail function that will take in two parameters, recipient mail address and a string. This string will be passed as a variable to our route where it will be compared with the string stored in the database

async function send_mail(recipient, string) {

}

The function would be asynchronous since we will need to wait for certain values

We need to get a new access token every time a mail is to be sent since the original token will expire within a limited amount of time

async function send_mail(recipient, string) {
    const accessToken = await oAuth2Client.getAccessToken();
    const {token} = accessToken;
}

The token variable is destructured from our accessToken object and will be used to send emails.

Now we must create a transport for setting up configurations

async function send_mail(recipient, string) {
    const accessToken = await oAuth2Client.getAccessToken();
    const {token} = accessToken;
    const transport = nodemailer.createTransport({
        service: "gmail", 
        host: 'smtp.gmail.com',
        port: 465,
        secure: true,
        auth: {
              type: "OAuth2",
              user: "YOUR_EMAIL_ADDRESS",
              cliendId: CLIENT_ID,
              clientSecret: CLIENT_SECRET,
              refreshToken: REFRESH_TOKEN,     
              accessToken: token
        }
    })
}

Before moving forward, let us design our email to be sent. HTML can be used for this. So let's create the HTML variable that will contain the message as well the confirmation link.

This link will send our string parameter as a query. When sending a value in a GET request, it is passed after the link, following a question mark.

So, if the variable greeting needs to be sent with the value hello, it will be sent like this

link.com/home?greeting=hello

With that in mind, let's design our email

const html = `
    <h1>Confirm your email</h1>
    <p>Hello user, here is your email confirmation link</p>
    <a href=http://www.YOUR_WEBSITE_ADDRESS.com/confirm_mail?random_string=${string}>Confirm mail</a>
`

Now, we are ready to send the mail

async function send_mail(recipient, string) {
    const accessToken = await oAuth2Client.getAccessToken();
    const {token} = accessToken;
    const transport = nodemailer.createTransport({
        service: "gmail", 
        host: 'smtp.gmail.com',
        port: 465,
        secure: true,
        auth: {
              type: "OAuth2",
              user: "YOUR_EMAIL_ADDRESS",
              cliendId: CLIENT_ID,
              clientSecret: CLIENT_SECRET,
              refreshToken: REFRESH_TOKEN,     
              accessToken: token
        }
    })

    const html = `
          <h1>Confirm your email</h1>
          <p>Hello user, here is your email confirmation link</p>
          <a href=http://www.YOUR_WEBSITE_ADDRESS.com/confirm_mail?random_string=${string}>Confirm mail</a>
    `

    const mailOptions = {
          from: "Website Name <YOUR_EMAIL_ADDRESS>",
          to: recipient,
          subject: Email Confirmation,
          html: html
    } 

    const result = await transport.sendMail(mailOptions);
}

In the above code, the html property is assigned our template literal variable that contains HTML code for render.

Make sure to replace the texts YOUR_EMAIL_ADDRESS and YOUR_WEBSITE_ADDRESS

Now that the send_mail function is created, we can call it when a new user hits the register POST route. To do this, we must be able to pass the two parameters to our function. The recipient email address can be retrieved from a form through the req object. But the second parameter must be a randomly generated string. This can be created using the following code

function generateRandomString(chars){
    const letterNums = "abcdefghijklmnopqrstuvwxyz0123456789"
    let string = "";
    for (let i=0; i < chars; i++){
        string += letterNums[Math.floor(Math.random() * 35)]
    }
    return string;
}

In the above code, chars is the number of characters you want your string to have

With everything set up, we can now create an alphanumeric string that will be sent by mail and will be stored on the database

First, when the register route is hit, a new user with confirmed being false is created. A new link record is added as well, with the 128 character string. The same string is sent using the send_mail function

const app = express();

app.post("/register", (req, res) => {
    const newUser = new User({
        email: req.body.email,
        confirmed: false
    });
    newUser.save();

    const randomString = generateRandomString(128);
    const newLink = new Link({
        email: req.body.email,
        random_string: randomString
    })
    newLink.save();
    send_mail(req.body.email, randomString);
})

app.listen(5000, () => console.log("Server running on port 5000"));

Now, when the link on the email is clicked, we will catch our string and find the corresponding email. This email will then have its confirmed property set to true. This must be done in our get request for confirm_mail (the route specified in our confirmation link)

app.get("/confirm_mail", (req, res) => {
    const confirmString = req.query.random_string;
    Link.findOne({ random_string: confirmString }, (err, response) => {
        if (response.email){
            User.findOneAndUpdate({ email: response.email }, { confirmed: true }, { new: true, useFindAndModify: false }, (err) => if (!err) { res.send("Email confirmed") });
        } else {
            res.send("Email not found");
        }
    })
})

All the code

const mongoose = require("mongoose");
const express = require("express");
const nodemailer = require("nodemailer");
const {google} = require("googleapis");

mongoose.connect("mongodb://localhost:27017/mainDB", {
    useNewUrlParser: true,
    useUnifiedTopology: true
});

const userSchema = new mongoose.Schema({
    email: String,
    confirmed: Boolean
})

const linkSchema = new mongoose.Schema({
    email: String,
    random_string: String
})

const User = mongoose.model("User", userSchema);
const Link = mongoose.model("Link", linkSchema);

function generateRandomString(chars){
    const letterNums = "abcdefghijklmnopqrstuvwxyz0123456789"
    let string = "";
    for (let i=0; i < chars; i++){
        string += letterNums[Math.floor(Math.random() * 35)]
    }
    return string;
}

async function send_mail(recipient, string) {
    const accessToken = await oAuth2Client.getAccessToken();
    const {token} = accessToken;
    const transport = nodemailer.createTransport({
        service: "gmail", 
        host: 'smtp.gmail.com',
        port: 465,
        secure: true,
        auth: {
              type: "OAuth2",
              user: "YOUR_EMAIL_ADDRESS",
              cliendId: CLIENT_ID,
              clientSecret: CLIENT_SECRET,
              refreshToken: REFRESH_TOKEN,     
              accessToken: token
        }
    })

    const html = `
          <h1>Confirm your email</h1>
          <p>Hello user, here is your email confirmation link</p>
          <a href=http://www.YOUR_WEBSITE_ADDRESS.com/confirm_mail?random_string=${string}>Confirm mail</a>
    `

    const mailOptions = {
          from: "Website Name <YOUR_EMAIL_ADDRESS>",
          to: recipient,
          subject: Email Confirmation,
          html: html
    } 

    const result = await transport.sendMail(mailOptions);
}    


const app = express();

app.post("/register", (req, res) => {
    const newUser = new User({
        email: req.body.email,
        confirmed: false
    });
    newUser.save();

    const randomString = generateRandomString(128);
    const newLink = new Link({
        email: req.body.email,
        random_string: randomString
    })
    newLink.save();
    send_mail(req.body.email, randomString);
})

app.get("/confirm_mail", (req, res) => {
    const confirmString = req.query.random_string;
    Link.findOne({ random_string: confirmString }, (err, response) => {
        if (response.email){
            User.findOneAndUpdate({ email: response.email }, { confirmed: true }, { new: true, useFindAndModify: false }, (err) => if (!err) { res.send("Email confirmed") });
        } else {
            res.send("Email not found");
        }
    })
})

app.listen(5000, () => console.log("Server running on port 5000"));

Conclusion

Ideally, for a more secure approach, you will hash the random_string using a package like bcrypt. That is it from this blog. Thank you for reading and happy coding !

ย