Как реализовать двухфакторную аутентификацию с помощью Node.JS и otplib
Вы когда-нибудь беспокоились о том, что хакеры могут получить доступ к вашим интернет-аккаунтам? С помощью двухфакторной аутентификации (2FA) вы можете избавиться от этих опасений. 2FA – это секретное оружие для создания дополнительного уровня защиты.
В этой статье мы расскажем вам, как просто интегрировать 2FA в ваши Node.js-приложения с помощью otplib, обеспечив пользователям эффективную безопасность и душевное спокойствие. Приготовьтесь повысить безопасность вашего приложения и доверие пользователей!
Понимание двухфакторной аутентификации
Двухфакторная аутентификация (2FA), также называемая двухэтапной проверкой или двойной аутентификацией, – это надежная мера безопасности, требующая от пользователей предоставления двух различных факторов аутентификации для подтверждения своей личности.
Когда 2FA активирована, при входе в систему вам будет предложено ввести дополнительный код, отправленный на ваш мобильный телефон, токен, сгенерированный Google или другим указанным источником, вместе с паролем. Этот дополнительный шаг значительно повышает безопасность вашего аккаунта, делая доступ к нему гораздо более сложным для хакеров, даже если они получили ваш пароль.
Важность 2FA
Надежный пароль может помочь защитить ваши учетные записи и данные, но в случае утечки информации вам нужен дополнительный уровень защиты. Именно здесь на помощь приходит 2FA. Вот несколько убедительных причин, по которым вам необходим 2FA:
- Защита ваших учетных записей в Интернете: 2FA защищает ваши онлайн-аккаунты от несанкционированного доступа.
- Дополнительный уровень безопасности: 2FA не ограничивается только именем пользователя и паролем, что значительно усложняет задачу хакеров по взлому ваших учетных записей.
- Повышение доверия потребителей: Внедрение 2FA повышает доверие потребителей к организациям и их предложениям, способствуя углублению отношений с клиентами и созданию благоприятного имиджа бренда.
- Повышенная защита от эволюционирующих угроз: 2FA обеспечивает повышенную защиту от развивающихся киберугроз, позволяя вам заблаговременно опережать потенциальных злоумышленников.
- Обеспечение спокойствия: Знание того, что ваши учетные записи имеют дополнительную меру защиты, дает вам душевное спокойствие.
Теперь перейдем к этапу разработки.
Настройка среды
Выполните следующую команду, чтобы создать каталог и перейти в него:
mkdir 2FA
cd 2FA
Далее:
npm init
Эта команда запрашивает несколько параметров, таких как имя и версия вашего приложения.
Далее:
npm install express body-parser otplib qrcode speakeasy
Эта команда устанавливает Express и все необходимые зависимости для запуска этого приложения, которые описаны ниже:
express
: Express – это фреймворк для создания веб-серверов и управления HTTP-запросами и ответами.body-parser
: Body-parser – это промежуточное ПО Express, которое анализирует тела входящих запросов перед передачей их обработчикам, делая их доступными через свойствоreq.body
. Он также используется для декодирования JSON и URL-кодированных данных.otplib
: Библиотека OTP (One Time Password) позволяет генерировать и проверять одноразовые пароли.qrcode
: QRCode – это пакет для создания QR-кодов. Он позволяет генерировать QR-коды, которые могут быть использованы для кодирования информации, такой как URL, текст или другие данные, которые могут быть отсканированы считывателем QR-кодов или камерой смартфона.speakeasy
: Speakeasy – это библиотека, которая генерирует и проверяет одноразовые пароли. В ней также есть функции создания резервных кодов и взаимодействия с другими методами аутентификации.
Регистрация пользователей
Теперь, когда все зависимости успешно установлены, создайте новую папку public
с файлом index.html
и добавьте в неё следующий код:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Register</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>2FA Authentication</h1>
<h2>Register</h2>
<form id="register-form">
<input
type="text"
id="register-username"
placeholder="Username"
required
/>
<input
type="password"
id="register-password"
placeholder="Password"
required
/>
<button type="submit">Register</button>
</form>
<p class="top-p">Have an account? <a href="login.html">Login</a></p>
<div id="message"></div>
<div id="qr-code"></div>
<script src="app.js"></script>
</body>
</html>
Приведенный выше код создает простую страницу регистрации для вашего приложения. Далее создайте новый файл в той же публичной директории под названием login.html
и добавьте в него следующий код:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>2FA Authentication</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>2FA Authentication</h1>
<h2>Login</h2>
<div id="message"></div>
<form id="login-form">
<input
type="text"
id="login-username"
placeholder="Username"
required
/>
<input
type="password"
id="login-password"
placeholder="Password"
required
/>
<button type="submit">Login</button>
</form>
<script src="app.js"></script>
</body>
</html>
</head>
</html>
Приведенный выше код создает страницу входа в систему. Последняя страница необходима для завершения дизайна фронтенда. Создайте новый файл в той же директории project/verify.html
и добавьте в него приведенный ниже код:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>2FA Authentication</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>2FA Authentication</h1>
<h2>Verify 2FA</h2>
<div id="message"></div>
<form id="verify-form">
<input
type="text"
id="verify-username"
placeholder="Username"
required
/>
<input
type="text"
id="verify-token"
placeholder="2FA Token"
required
/>
<button type="submit">Verify</button>
</form>
<script src="app.js"></script>
</body>
</html>
</head>
</html>
Этот код создает страницу для проверки зарегистрированных пользователей. Теперь улучшите дизайн фронтенда, добавив стили. Создайте новый файл с именем style.css
в каталоге проекта и добавьте в него следующий код:
body {
font-family: Arial, sans-serif;
margin: 10rem 25rem;
justify-content: center;
text-align: center;
background-color: snow;
}
form {
margin-bottom: 10px;
}
input[type="text"],
input[type="password"] {
border-radius: 10px;
align-items: center;
outline: none;
display: block;
margin: 10px 0;
padding: 10px;
width: 100%;
}
button[type="submit"] {
position: absolute;
border-radius: 10px;
align-items: center;
outline: none;
margin: 5px 10px 10px -130px;
padding: 10px;
width: 20%;
}
#qr-code {
margin-top: 10px;
}
p.top-p {
margin-top: 55px;
}
p {
margin-top: 5px;
}
#message {
color: red;
}
Активация 2FA
Чтобы убедиться, что все зависимости установлены, откройте файл package.json
и вы должны увидеть точное изображение ниже:
Если всё это есть в вашем JSON-файле, то всё готово. Теперь создайте новый файл с именем index.js
и вставьте в него следующий код:
const express = require("express");
const bodyParser = require("body-parser");
const otplib = require("otplib");
const qrcode = require("qrcode");
const path = require("path");
const crypto = require("crypto");
const app = express();
app.use(bodyParser.json());
const users = {};
Этот код устанавливает необходимые библиотеки для работы с 2FA и создает локальное хранилище для хранения информации о пользователе, когда тот хочет зарегистрироваться.
Генерация секретного ключа
Поскольку у вас есть все зависимости, необходимые для запуска этого приложения, вы можете сгенерировать секретный ключ без каких-либо ошибок. Чтобы сгенерировать секретный ключ, добавьте следующий код в файл index.js
.
app.post("/register", (req, res) => {
const { username, password } = req.body;
if (users[username]) {
return res.status(400).send("User already exists");
}
const secret = otplib.authenticator.generateSecret();
users[username] = { password, secret };
});
Приведенный выше код генерирует секретный ключ с помощью библиотеки otplib в конечной точке register
.
Проверка 2FA
Чтобы проверить код 2FA, вам нужно расширить текущую настройку и включить маршрут для проверки OTP (One-Time Password). Теперь обновите файл index.js
с помощью приведенного ниже кода:
app.post("/register", (req, res) => {
const { username, password } = req.body;
if (users[username]) {
return res.status(400).send("User already exists");
}
const secret = otplib.authenticator.generateSecret();
users[username] = { password, secret };
qrcode.toDataURL(
otplib.authenticator.keyuri(username, "MyApp", secret),
(err, imageUrl) => {
if (err) {
return res.status(500).send("Error generating QR code");
}
res.send({ secret, imageUrl });
},
);
});
app.post("/verify-2fa", (req, res) => {
const { username, token } = req.body;
const user = users[username];
if (!user) {
return res.status(400).send("User not found");
}
const isValid = otplib.authenticator.check(token, user.secret);
if (!isValid) {
return res.status(401).send("Invalid 2FA token");
}
res.send("2FA verification successful");
});
Приведенный выше код выполняет несколько задач: создает URI с помощью секретного ключа, генерирует QR-код и преобразует его в изображение с помощью URI. Кроме того, конечная точка verify-2fa
проверяет токен 2FA, предоставленный пользователем. Он извлекает имя пользователя и токен, получает данные пользователя и использует otplib
для проверки токена 2FA.
Использование резервных кодов
Резервный код служит альтернативным способом аутентификации в случае, если пользователь потерял доступ к своему основному устройству 2FA. Эти коды предоставляются пользователям в крайнем случае и должны храниться в тайне. Чтобы реализовать это, перейдите к файлу index.js
и обновите код следующим образом:
function generateBackupCodes() {
const codes = [];
for (let i = 0; i < 10; i++) {
codes.push(crypto.randomBytes(4).toString("hex"));
}
return codes;
}
app.post("/register", async (req, res) => {
const { username, password } = req.body;
if (users[username]) {
return res.status(400).send("User already exists");
}
const hashedPassword = password;
const secret = otplib.authenticator.generateSecret();
const backupCodes = generateBackupCodes();
users[username] = { password: hashedPassword, secret, backupCodes };
qrcode.toDataURL(
otplib.authenticator.keyuri(username, "MyApp", secret),
(err, imageUrl) => {
if (err) {
return res.status(500).send("Error generating QR code");
}
res.send({ secret, imageUrl, backupCodes });
},
);
});
app.post("/verify-2fa", (req, res) => {
const { username, token } = req.body;
const user = users[username];
if (!user) {
return res.status(400).send("User not found");
}
const isValid = otplib.authenticator.check(token, user.secret);
if (!isValid) {
const backupCodeIndex = user.backupCodes.indexOf(token);
if (backupCodeIndex === -1) {
return res.status(401).send("Invalid 2FA token or backup code");
}
user.backupCodes.splice(backupCodeIndex, 1);
}
res.send("2FA verification successful");
});
Обновленный код генерирует резервные коды и отправляет ответ, если регистрация пользователя прошла успешно.
Интеграция потока аутентификации пользователей
Чтобы интегрировать рабочий процесс аутентификации пользователей, создайте файл с именем app.js
в каталоге project
и добавьте в него следующий код:
document.addEventListener("DOMContentLoaded", () => {
const registerForm = document.getElementById("register-form");
const loginForm = document.getElementById("login-form");
const verifyForm = document.getElementById("verify-form");
const messageDiv = document.getElementById("message");
const qrCodeDiv = document.getElementById("qr-code");
if (registerForm) {
registerForm.addEventListener("submit", async (e) => {
e.preventDefault();
const username = document.getElementById("register-username").value;
const password = document.getElementById("register-password").value;
try {
const response = await fetch("/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, password }),
});
if (response.ok) {
const data = await response.json();
qrCodeDiv.innerHTML = `
<img src="${data.imageUrl}" alt="QR Code">
<p>Secret: ${data.secret}</p>`;
messageDiv.innerHTML = `
<p>Registration successful.</p>
<p>Backup Codes (keep these safe):</p>
<ul>${data.backupCodes.map((code) => `<li style="list-style-type: none;">${code}</li>`).join("")}</ul>`;
} else {
const error = await response.text();
messageDiv.innerText = `Error: ${error}`;
}
} catch (error) {
messageDiv.innerText = `Error: ${error.message}`;
}
});
}
if (loginForm) {
loginForm.addEventListener("submit", async (e) => {
e.preventDefault();
const username = document.getElementById("login-username").value;
const password = document.getElementById("login-password").value;
try {
const response = await fetch("/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, password }),
});
if (response.ok) {
messageDiv.innerText =
"Login successful. Redirecting to 2FA verification...";
setTimeout(() => {
window.location.href = "verify.html";
}, 1000);
} else {
const error = await response.text();
messageDiv.innerText = `Error: ${error}`;
}
} catch (error) {
messageDiv.innerText = `Error: ${error.message}`;
}
});
}
if (verifyForm) {
verifyForm.addEventListener("submit", async (e) => {
e.preventDefault();
const username = document.getElementById("verify-username").value;
const token = document.getElementById("verify-token").value;
try {
const response = await fetch("/verify-2fa", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, token }),
});
if (response.ok) {
messageDiv.innerText = "2FA verification successful";
} else {
const error = await response.text();
messageDiv.innerText = `Error: ${error}`;
}
} catch (error) {
messageDiv.innerText = `Error: ${error.message}`;
}
});
}
});
Предоставленный код предназначен для управления взаимодействием с пользователями на странице регистрации, входа и верификации вашего приложения. Он прослушивает отправку форм и выполняет асинхронные запросы к внутреннему серверу для обработки процессов регистрации, входа и проверки 2FA. Наконец, обновите файл index.js
, добавив в него следующий код:
const express = require("express");
const bodyParser = require("body-parser");
const otplib = require("otplib");
const qrcode = require("qrcode");
const path = require("path");
const crypto = require("crypto");
const app = express();
app.use(bodyParser.json());
const users = {};
function generateBackupCodes() {
const codes = [];
for (let i = 0; i < 10; i++) {
codes.push(crypto.randomBytes(4).toString("hex"));
}
return codes;
}
app.post("/register", async (req, res) => {
const { username, password } = req.body;
if (users[username]) {
return res.status(400).send("User already exists");
}
const hashedPassword = password;
const secret = otplib.authenticator.generateSecret();
const backupCodes = generateBackupCodes();
users[username] = { password: hashedPassword, secret, backupCodes };
qrcode.toDataURL(
otplib.authenticator.keyuri(username, "MyApp", secret),
(err, imageUrl) => {
if (err) {
return res.status(500).send("Error generating QR code");
}
res.send({ secret, imageUrl, backupCodes });
},
);
});
app.post("/login", (req, res) => {
const { username, password } = req.body;
const user = users[username];
if (!user || user.password !== password) {
return res.status(401).send("Invalid username or password");
}
res.send("Login successful. Please verify with 2FA");
});
app.post("/verify-2fa", (req, res) => {
const { username, token } = req.body;
const user = users[username];
if (!user) {
return res.status(400).send("User not found");
}
const isValid = otplib.authenticator.check(token, user.secret);
if (!isValid) {
const backupCodeIndex = user.backupCodes.indexOf(token);
if (backupCodeIndex === -1) {
return res.status(401).send("Invalid 2FA token or backup code");
}
user.backupCodes.splice(backupCodeIndex, 1);
}
res.send("2FA verification successful");
});
app.use(express.static(path.join(__dirname, "public")));
app.listen(3000, () => {
console.log("Server running on port 3000");
});
В приведенном выше коде весь файл index.js
представлен в одном блоке для удобства использования. Также был добавлен путь к серверу для доступа к вашему приложению.
Тестирование
Перед началом тестирования убедитесь, что на вашем устройстве установлен Google Authenticator.
Чтобы запустить сервер, выполните следующую команду:
node index.js
Ответ на изображении показывает, что ваш сервер успешно работает. Далее введите в браузере адрес http://localhost:3000
.
Изображение выше демонстрирует успешную регистрацию пользователя. Вы можете либо отсканировать QR-код, либо вручную ввести секретный ключ в приложение Google Authenticator. Кроме того, в крайнем случае можно воспользоваться резервным кодом.
На изображении показан QR-код токена для tj24. Далее войдите в систему и введите свои данные, как показано на скриншоте ниже:
Ролик выше демонстрирует успешную проверку при использовании Google Authenticator для сканирования QR-кода. Попробуйте использовать ключ настройки и резервный код; вы также получите положительный ответ.
Примечание: База данных работает на локальном хранилище.
Заключение
Поздравляем, вы успешно интегрировали 2FA с Node.js и otplib. Применяя описанные в этой статье техники, такие как генерация токенов на основе QR-кода, секретные ключи, otplib и резервные коды, вы можете создать систему, которая проста в развертывании и при этом обеспечивает значительные преимущества в плане безопасности.
Начните создавать свою систему 2FA прямо сейчас и сделайте первый шаг к более безопасной цифровой среде!