If you have thought about building any dashboard, you probably realize you need to implement authentication. You are probably already familiar with terms like Login and Registration.
In today’s world, more companies are pushing to secure your account and offer you to add a Two-Factor authentication.Two-factor authentication is an extra layer of protection; it requires you to enter a code you can find in an external service, SMS, Email, or an Authentication App.
In this article, you’ll learn how to build an application that uses two-factor authentication with React, Novu, and Node.js.
Two Factor Authentication – sometimes referred to as dual-factor authentication, is an additional security measure that allows users to confirm their identity before gaining access to an account.It can be implemented through a hardware token, SMS Text Messages, Push notifications, and biometrics, required by the application before users can be authorized to perform various actions.
In this article, we’ll use the SMS text messaging 2FA by creating an application that accepts the users’ credentials and verifies their identity before granting them access to the application.
Just a quick background about us. Novu is the first open-source notification infrastructure. We basically help to manage all the product notifications. It can be In-App (the bell icon like you have in the Dev Community – Websockets), Emails, SMSs and so on.
Create the project folder containing two sub-folders named client and server.
mkdir auth-systemcd auth-systemmkdir client server
Navigate into the server folder and create a package.json file.
cd server & npm init -y
Install Express.js, CORS, and Nodemon.
npm install express cors nodemon
Express.js is a fast, minimalist framework that provides several features for building web applications in Node.js. CORS is a Node.js package that allows communication between different domains. Nodemon is a Node.js tool that automatically restarts the server after detecting file changes.
Create an index.js file – the entry point to the web server.
In this section, we’ll build the user interface for the application allowing users to register and sign in to an application. Users can create an account, log in, and perform 2FA via SMS before they are authorised to view the dashboard.
Create a new React.js project within the client folder.
cd clientnpx create-react-app ./
Delete the redundant files such as the logo and the test files from the React app, and update the App.js file to display Hello World as below.
Here, we’ll create the authentication workflow for the application.When creating an account, the application accepts the user’s email, username, telephone number, and password. Then redirects the user to the sign-in page, where the email and password are required. The application sends a verification code to the user’s phone number to verify their identity before viewing the dashboard page.
Create a POST route within the index.js file on the server that accepts the user’s credentials.
app.post("/api/register", (req, res) => { const { email, password, tel, username } = req.body; //\ud83d\udc47\ud83c\udffb Logs the credentials to the console console.log({ email, password, tel, username });})
Since we need to save the user’s credentials, update the POST route as below:
//\ud83d\udc47\ud83c\udffb An array containing all the usersconst users = [];//\ud83d\udc47\ud83c\udffb Generates a random string as the IDconst generateID = () => Math.random().toString(36).substring(2, 10);app.post("/api/register", (req, res) => { //\ud83d\udc47\ud83c\udffb Get the user's credentials const { email, password, tel, username } = req.body; //\ud83d\udc47\ud83c\udffb Checks if there is an existing user with the same email or password let result = users.filter((user) => user.email === email || user.tel === tel); //\ud83d\udc47\ud83c\udffb if none if (result.length === 0) { //\ud83d\udc47\ud83c\udffb creates the structure for the user const newUser = { id: generateID(), email, password, username, tel }; //\ud83d\udc47\ud83c\udffb Adds the user to the array of users users.push(newUser); //\ud83d\udc47\ud83c\udffb Returns a message return res.json({ message: "Account created successfully!", }); } //\ud83d\udc47\ud83c\udffb Runs if a user exists res.json({ error_message: "User already exists", });});
Update the postSignUpDetails function within the Signup component to notify users that they have signed up successfully.
The code snippet above checks if the data returned from the server contains an error message before navigating to the log-in route. If there is an error, it displays the error message.
Create a POST route on the server that authenticates the user.
app.post("/api/login", (req, res) => { //\ud83d\udc47\ud83c\udffb Accepts the user's credentials const { email, password } = req.body; //\ud83d\udc47\ud83c\udffb Checks for user(s) with the same email and password let result = users.filter( (user) => user.email === email && user.password === password ); //\ud83d\udc47\ud83c\udffb If no user exists, it returns an error message if (result.length !== 1) { return res.json({ error_message: "Incorrect credentials", }); } //\ud83d\udc47\ud83c\udffb Returns the username of the user after a successful login res.json({ message: "Login successfully", data: { username: result[0].username, }, });});
Update the postLoginDetails to display the response from the server.
const postLoginDetails = () => { fetch("http://localhost:4000/api/login", { method: "POST", body: JSON.stringify({ email, password, }), headers: { "Content-Type": "application/json", }, }) .then((res) => res.json()) .then((data) => { if (data.error_message) { alert(data.error_message); } else { //\ud83d\udc47\ud83c\udffb Logs the username to the console console.log(data.data); //\ud83d\udc47\ud83c\udffb save the username to the local storage localStorage.setItem("username", data.data.username); //\ud83d\udc47\ud83c\udffb Navigates to the 2FA route navigate("/phone/verify"); } }) .catch((err) => console.error(err));};
The code snippet above displays the error message to the user if there is an error; otherwise, it saves the username gotten from the server to the local storage for easy identification.
In the upcoming sections, I’ll guide you through adding the SMS two-factor authentication using Novu.
Novu allows us to add various forms of notifications, such as SMS, email, chat, and push messages to your software applications.
To install the Novu Node.js SDK, run the code snippet below on your server.
npm install @novu/node
Create a Novu project by running the code below. A personalised dashboard is available to you.
npx novu init
You will need to sign in with Github before creating a Novu project. The code snippet below contains the steps you should follow after running npx novu init
Now let's setup your account and send your first notification\u2753 What is your application name? Devto Clone\u2753 Now lets setup your environment. How would you like to proceed? > Create a free cloud account (Recommended)\u2753 Create your account with: > Sign-in with GitHub\u2753 I accept the Terms and Condidtions (https://novu.co/terms) and have read the Privacy Policy (https://novu.co/privacy) > Yes\u2714\ufe0f Create your account successfully.We've created a demo web page for you to see novu notifications in action.Visit: http://localhost:57807/demo to continue
Visit the demo web page http://localhost:57807/demo, copy your subscriber ID from the page, and click the Skip Tutorial button. We’ll be using it later in this tutorial.
Copy your API key available within the Settings section under API Keys on the Novu Manage Platform.
Import Novu from the package and create an instance using your API Key on the server.
//\ud83d\udc47\ud83c\udffb Within server/index.jsconst { Novu } = require("@novu/node");const novu = new Novu("");
Novu supports several SMS text messaging tools such as Twilio, Nexmo, Plivo, Amazon SNS, and many others. In this section, you’ll learn how to add Twilio SMS messaging to Novu.
Go to the Twilio homepage and create an account. You will have to verify your email and phone number.
Head over to your Twilio Console once you’re signed in.
Generate a Twilio phone number on the dashboard. This phone number is a virtual number that allows you to communicate via Twilio.
Copy the Twilio phone number somewhere on your computer; to be used later in the tutorial.
Scroll down to the Account Info section, and copy and paste the Account SID and Auth Token somewhere on your computer. (to be used later in this tutorial).
Select Integrations Store from the sidebar of your Novu dashboard and scroll down to the SMS section.
Choose Twilio and enter the needed credentials provided by Twilio, then click Connect.
Next, create a notification template by selecting Notifications on the sidebar.
Select Workflow Editor from the side pane and create a workflow as below:
Click the SMS from the workflow and add the text below to the message content field.
Your verification code is {{code}}
Novu allows you to add dynamic content or data to the templates using the Handlebars templating engine. The data for the code variable will be inserted into the template as a payload from the request.
Go back to the index.js file on the server and create a function that sends an SMS to verify the users when they log in to the application. Add the code below into the index.js file:
//\ud83d\udc47\ud83c\udffb Generates the code to be sent via SMSconst generateCode = () => Math.random().toString(36).substring(2, 12);const sendNovuNotification = async (recipient, verificationCode) => { try { let response = await novu.trigger("", { to: { subscriberId: recipient, phone: recipient, }, payload: { code: verificationCode, }, }); console.log(response); } catch (err) { console.error(err); }};
The code snippet above accepts the recipient’s telephone number and the verification code as a parameter.
Update the login POST route to send the SMS via Novu after a user logs in to the application.
//\ud83d\udc47\ud83c\udffb variable that holds the generated codelet code;app.post("/api/login", (req, res) => { const { email, password } = req.body; let result = users.filter( (user) => user.email === email && user.password === password ); if (result.length !== 1) { return res.json({ error_message: "Incorrect credentials", }); } code = generateCode(); //\ud83d\udc47\ud83c\udffb Send the SMS via Novu sendNovuNotification(result[0].tel, code); res.json({ message: "Login successfully", data: { username: result[0].username, }, });});
To verify the code entered by the user, update the PhoneVerify component to send the code to the server.
So far, you’ve learnt what two-factor authentication is, its various forms, and how to add it to a web application.
Two Factor Authentication is added to software applications to protect both the user credentials and the resources users can access. Depending on your application, you can add 2FA at specific parts where significant changes occur.