How to Implement Magic Link in Node.JS With Nodemailer, and Gmail

Ogubuike Alexandra
7 min readFeb 18, 2023

--

Security breaches are becoming more prevalent. It’s essential to safeguard your personal data and keep it away from prying eyes.

One of the most common ways to protect your online accounts is by using passwords. However, with the rise of hacking techniques and data breaches, even the most robust passwords can be compromised.

As a solution, passwordless authentication has gained popularity in recent years.

In this type of authentication, users can log in to their accounts without the need for a password. Instead, they receive a secure token via email, SMS, or push notifications, which they use to authenticate themselves.

This approach is not only more secure but also more convenient for users. In this article, we will look at

  • Creating an express server
  • How to set up nodemailer
  • How to configure Gmail to work with NodeJS and nodemailer
  • How to send magic link via email in NodeJS
  • How to verify magic link sent via email in NodeJs

If you want to implement passwordless authentication in your Node.JS application, you’ve come to the right place.

We’ll look at how to use Node.JS, Nodemailer, and Gmail to create a passwordless authentication system that sends secure tokens via email.

With this system, you can enhance the security of your application and provide a seamless login experience for your users. So, let’s get started!

CREATE EXPRESS APPLICATION

To set the engine running, let's create a very basic express application.

First, we will create a new folder:

mkdir passwordless-auth
cd passwordless-auth

Inside this new folder, we initialize a package.json file:

npm init -y 

Next, we install Express:

npm i express

Then we create an index.js file. To keep the focus on the topic we will add code for a minimal server inside index.js:

const express = require("express");

const app = express();
app.use(express.json());

app.get("/", (_, res) =>
res.status(200).send({
message: "Welcome, passwordless genius!",
})
);

app.listen(7010, () => {
console.log("Listening on 7010");
});

We will run this in the terminal to confirm that our server is working:

node index.js

We should be greeted by this message in our terminal:

Listening on port 7010

Let's go to the cool stuff, sending the emails.

For that, we will use Nodemailer. Nodemailer is a module that allows us to send emails straight up from our server.
How do we set it up?🤔.

How to Setup Nodemailer In a NodeJS Application

We will start this setup by installing nodemailer:

npm i nodemailer

The Nodemailer API requires that we create a transporter object. The job of the transporter is simply to send mail. We will create a new file, called service.js. This is where we will put our helper methods.

Let’s import nodemailer at the top of our service.js file:

const nodeMailer = require("nodemailer");

The first helper method we will create is getTransport() which will return a transport object:

exports.getTransport = () => nodeMailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_ADDRESS,
pass: process.env.EMAIL_PASSWORD
}
});

Notice that we are using environment variables to store sensitive data. If you are having issues setting up and using a .env file, here is a short step-by-step guide.
The next step is to configure Gmail to work with nodemailer.

How to Configure Gmail For NodeJs and Nodemailer

To be able to use Gmail with nodemailer, we need to get some sort of authorization. From our Google account, we can set up something called an app-password. The app-password works like a regular password. It allows us to connect and work with third-party email clients.

First, we need to enable 2-step verification from the security tab in our Google account:

Once that's done, we proceed to the app-password option under the security tab. Select a custom name under the Select app dropdown and then select a device from the Select device dropdown:

Finally, we click on generate to get a 16-digit password that we will use as the email password in our application. This password will only be visible once, so we have to copy it and store it somewhere:

In our .env file, we will simply change the value of EMAIL_PASSWORD to the generated password:

EMAIL_PASSWORD=<16-digitGeneratedpassword>
EMAIL_ADDRESS=<theEmailAddressWeAreWorkingWith>

At this point, we have crossed two major milestones — setting up nodemailer and configuring Gmail.

Now, let’s complete our application.

How to Send and Verify Magic Link In NodeJS

The overall flow for our app can be divided into two steps

  • Step One: When a user tries to log in using just their email, we will send them a magic link via their email
  • Step Two: When the user clicks on the link we sent to their email, the user is verified and authenticated

So we basically need two routes, one for login/sending email and the other for verification.

How to send magic link via email in NodeJs

To send the user a verifiable token, we will use the jsonwebtoken library.
Let's install it:

npm install jsonwebtoken

Next, we will import it into our service.js file:

const jwt = require("jsonwebtoken");

Then in our service.js, we will create a function that collects an email and generates a signed token for us:

exports.generateToken = (email) => {
const expirationDate = new Date();
expirationDate.setMinutes(new Date().getMinutes() + 45);
return jwt.sign({ email, expirationDate }, process.env.JWT_SECRET_KEY);
};

In the code, we set the expiration time of the generated token to be after 45 minutes. Then we use the expiration time and the email to sign the token.
We also added a field titled JWT_SECRET_KEY in our .env— This can be any number or text.

After the generateToken function, we will create the getMailOptions function which takes in an email and a link and returns a mail option object:

exports.getMailOptions = (email, link) => {
let body = `
<h2>Hey ${email}</h2>
<p>Here's the special magic link you requested:</p>
<p>${link}</p>
<p>Please note that for added security this link becomes invalid after 45 minutes</p>
<p>Stay Jiggy</p>`;

return {
body,
subject: "Urgent: Super Secret Magic Link",
to: email,
html: body,
from: process.env.EMAIL_ADDRESS,
};
};

To tie it all up, we will import generateToken, getMailOptions, and getTransport into the index.js file:

const { generateToken, getMailOptions, getTransport } = require("./service.js");

Next, we create the login route:

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

//Get email from request body
const { email } = req.body;
if (!email) {
res.status(400).send({
message: "Invalid email address.",
});
}

//Prepare variables
const token = generateToken(email);
const link = `http://localhost:7010/verify?token=${token}`;

//Create mailrequest
let mailRequest = getMailOptions(email, link);

//Send mail
return getTransport().sendMail(mailRequest, (error) => {
if (error) {
res.status(500).send("Can't send email.");
} else {
res.status(200);
res.send({
message: `Link sent to ${email}`,
});
}
});
});

In the code, we first validated the input email address. Next, we got the token and mailRequest object. Finally, we used the transporter’s sendMail function to send the mail.

How To Verify Magic Link Sent Via Email In NodeJS

We finalize by adding the route that will verify our generated tokens:

app.get("/verify", (req, res) => {
const { token } = req.query;
if (!token) {
res.status(401).send("Invalid user token");
return;
}

let decodedToken;
try {
decodedToken = jwt.verify(token, process.env.JWT_SECRET_KEY);
} catch {
res.status(401).send("Invalid authentication credentials");
return;
}

if (
!decodedToken.hasOwnProperty("email") ||
!decodedToken.hasOwnProperty("expirationDate")
) {
res.status(401).send("Invalid authentication credentials.");
return;
}

const { expirationDate } = decodedToken;
if (expirationDate < new Date()) {
res.status(401).send("Token has expired.");
return;
}
res.status(200).send("verfication successful");
});

In the code, we retrieve the token from the request query, and then we try to decode it. After that, we try to retrieve the email and expiration date from the decoded code.

If we cannot find either the email or expiration date, we throw an error. Finally, we are checking that the token has not expired. After successful verification, we reply with a success message.

Conclusion:

In this exciting article, we looked at how to send magic links using nodemailer and Gmail in NodeJS.

We started the article by creating a minimal server. Then we looked at how to set up nodemailer and also how to configure our Gmail account to work with nodemailer.

Then we tied it all up by creating two routes — for sending a verifiable token via mail and for verifying the token that has been sent.

--

--

Ogubuike Alexandra

Founder @ Codetivite | Senior Backend Engineer | Technical Writer / OpenSource Contributor @ CodeMaze