Two Factor Authentication with NextJS

Farmaan
4 min readJun 15, 2024

--

In this blog, you’ll find how to implement Two-Factor Authentication (2FA) using an authenticator app in a Next.js application. It will guide you through the integrating of 2FA functionality and ensuring a secure authentication process.

Step 1: Install required dependencies

npm i speakeasy qrcode

Step 2: Create the Backend APIs

Create files in the paths shown below and include the following code lines.

~app/api/2fa/qrcode/route.js~

import QRCode from "qrcode";
import speakeasy from "speakeasy";

export async function GET() {

const secret = speakeasy.generateSecret({
name: "You can give your app name here",
});

const data = await QRCode.toDataURL(secret.otpauth_url);

return Response.json({ data, secret: secret.base32, status: 200 });
}

~app/api/2fa/verify/route.js~

import { NextRequest } from "next/server";
import speakeasy from "speakeasy";

import { getServerSession } from "next-auth/next"

export async function POST(req, res) {

const { secret, token } = await req.json();

const session = await getServerSession(authOptions)

// Here, we have to implement 2 strategies
// 1. Verifying during LOGIN
// 2. Enabling 2FA for the first time

// 1. Verifying during LOGIN
if (!session) {

let decrypted_secret = await decrypt(secret) // Have a function to decrypt your secret key

const verified = speakeasy.totp.verify({
secret: decrypted_secret, // Secret Key
encoding: "base32",
token: token, // OTP Code
});

return Response.json({ verified });

} else {

// 2. Enabling 2FA for the first time
const verified = speakeasy.totp.verify({
secret: secret, // Secret Key
encoding: "base32",
token: token, // OTP Code
});

if (verified) {
// save the secret in your database
// Don't forget to encrypt it
}

return Response.json({ verified });

}

}

Step 3: Create the frontend (Enabling 2FA)

'use client';
import React, { useState, useEffect, useRef } from 'react';
import axios from "axios";

const BASE_URL = process.env.BASE_URL;

const TwoFactorModal = () => {

const [otp, setOtp] = useState('');
const [invalidOtp, setInvalidOtp] = useState(false)

const [qrImage, setQrImage] = useState()
const [secret, setSecret] = useState()

/* Generate a QR */
const get2faQrCode = async () => {
const response = await axios.get(`${BASE_URL}api/2fa/qrcode`,
{
headers: {
"Content-Type": "application/json"
}
}
)

if (response.data.status == 200) {
setQrImage(response.data.data)
setSecret(response.data.secret)
}
}

useEffect(() => {
get2faQrCode()
}, [])

/* Validate Code */
const handleOtpChange = async (e) => {

setOtp(e.target.value);

if (e.target.value.length === 6) {

const token = e.target.value
const response = await axios.post(`${BASE_URL}api/2fa/verify`,
{ secret, token },
{
headers: {
"Content-Type": "application/json"
}
}
)

if (response.data.verified) {
// 2FA Enabled successfully
} else {
setInvalidOtp(true)
}

}

};

return (

<div className='flex justify-end w-full'>

<div className="container mx-auto p-4">

<div className="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4">

<div className="flex flex-1 justify-center items-center p-4 text-white rounded-md">

{
qrImage &&
<img src={qrImage} alt="2FA QR Code" className='rounded-lg border-2' />
}

</div>

<div className="flex-1 p-4 text-white rounded-md">

<p className="text-2xl text-gray-700 font-bold mb-4">Use an Authenticator App to enable 2FA</p>
<ul className="list-none list-inside mb-4 text-gray-700">
<li className="mb-2"><span className="font-bold">Step 1:</span> Scan the QR Code with your Authenticator app.</li>
<li className="mb-2"><span className="font-bold">Step 2:</span> Enter the code below from your app.</li>
</ul>

{/* OTP Input */}
<input
type="text"
maxLength="6"
value={otp}
onChange={handleOtpChange}
/>

{/* Invalid Input */}
{
<p className="mt-3 text-red-500 text-sm text-center">{invalidOtp && '*Invalid Code'}</p>
}

</div>

</div>

</div>

</div>

);
};

export default TwoFactorModal;
For demonstration I have shown Google Authenticator. But it works with any authenticator app.

Step 4: Create the frontend (Verifying during login process)

I hope you already have a strategy to login. So, during the login you will have to verify if the user has enabled 2FA. If enabled, retrieve the secret key and trigger the below api to verify the code.

const response = await axios.post(`${BASE_URL}api/2fa/verify`,
{ secret, token },
{
headers: {
"Content-Type": "application/json"
}
}
)

if (response.data.verified) {
// 2FA code Verified successfully
} else {
// 2FA code verification failed
}

Thank you for taking the time to read this guide on implementing 2FA with an authenticator app in Next.js. I hope you found it helpful and informative. If you have any questions or feedback, please feel free to leave a comment.

--

--