DevGang
Авторизоваться

Как реализовать двухфакторную аутентификацию с помощью 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 прямо сейчас и сделайте первый шаг к более безопасной цифровой среде!

Источник:

#JavaScript #NodeJS
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу