Java, UX, HTML, CSS, WEB-design

Создание безопасных потоков паролей с помощью NodeJS и MySQL

[ad_1]

  • Даршан Сомашекар

  • 0 Комментарии

Создание безопасных потоков паролей с помощью NodeJS и MySQL

  • 11 минут чтения

  • Node.js, аутентификация, JavaScript, безопасность

Краткое резюме ↬

Функциональность сброса пароля — это стол ставок для любого удобного приложения. Это также может быть кошмаром безопасности. Используя NodeJS и MySQL, Даршан демонстрирует, как успешно создать безопасный поток сброса пароля, чтобы вы могли избежать этих ловушек.

Если вы чем-то похожи на меня, вы не раз забывали свой пароль, особенно на сайтах, которые вы давно не посещали. Вы, вероятно, также видели и / или были огорчены электронными письмами для сброса пароля, которые содержат ваш пароль в виде обычного текста.

К сожалению, рабочему процессу сброса пароля уделяется мало внимания при разработке приложений. Это не только может привести к разочарованию пользователя, но также может оставить ваше приложение с зияющими дырами в безопасности.

Мы собираемся рассказать, как создать безопасный рабочий процесс сброса пароля. Мы будем использовать NodeJS и MySQL в качестве базовых компонентов. Если вы пишете с использованием другого языка, платформы или базы данных, вы все равно можете воспользоваться общими «Советами по безопасности», изложенными в каждом разделе.

Процесс сброса пароля состоит из следующих компонентов:

  • Ссылка для отправки пользователя к началу рабочего процесса.
  • Форма, которая позволяет пользователю отправить свою электронную почту.
  • Поиск, который проверяет электронную почту и отправляет электронную почту на адрес.
  • Электронное письмо, содержащее токен сброса с истечением срока действия, который позволяет пользователю сбросить свой пароль.
  • Форма, позволяющая пользователю создать новый пароль.
  • Сохранение нового пароля и разрешение пользователю снова войти в систему с новым паролем.

Помимо Node, Express и MySQL, мы будем использовать следующие библиотеки:

  • Упорядочить ORM
  • Nodemailer

Sequelize — это ORM базы данных NodeJS, который упрощает выполнение миграции базы данных, а также создание запросов безопасности. Nodemailer — это популярная библиотека электронной почты NodeJS, которую мы будем использовать для отправки электронных писем для сброса пароля.

Совет по безопасности №1

В некоторых статьях предлагается разработать безопасные потоки паролей с использованием веб-токенов JSON (JWT), которые устраняют необходимость в хранилище базы данных (и, следовательно, их проще реализовать). Мы не используем этот подход на нашем сайте, потому что секреты токенов JWT обычно хранятся прямо в коде. Мы хотим избежать «одного секрета», чтобы управлять ими всеми (по той же причине, по которой вы не добавляете пароли с одним и тем же значением), и поэтому нам нужно переместить эту информацию в базу данных.

Еще после прыжка! Продолжить чтение ниже ↓

Установка

Сначала установите Sequelize, Nodemailer и другие связанные библиотеки:

$ npm install --save sequelize sequelize-cli mysql crypto nodemailer

В маршрут, в который вы хотите включить рабочие процессы сброса, добавьте необходимые модули. Если вам нужно освежить знания об экспрессе и маршрутах, ознакомьтесь с их руководством.

const nodemailer = require('nodemailer');

И настройте его с вашими учетными данными SMTP электронной почты.

const transport = nodemailer.createTransport({
    host: process.env.EMAIL_HOST,
    port: process.env.EMAIL_PORT,
    secure: true,
    auth: {
       user: process.env.EMAIL_USER,
       pass: process.env.EMAIL_PASS
    }
});

Решение для электронной почты, которое я использую, — это простая служба электронной почты AWS, но вы можете использовать что угодно (Mailgun и т. д.).

Если вы впервые настраиваете службу отправки электронной почты, вам потребуется потратить некоторое время на настройку соответствующих ключей домена и настройку авторизации. Если вы используете Route 53 вместе с SES, это очень просто и выполняется практически автоматически, поэтому я выбрал его. У AWS есть несколько учебных пособий о том, как SES работает с Route53.

Совет по безопасности №2

Чтобы хранить учетные данные отдельно от моего кода, я использую dotenv, который позволяет мне создать локальный файл .env с моими переменными среды. Таким образом, при развертывании в рабочей среде я могу использовать разные производственные ключи, которые не видны в коде, и, следовательно, позволяет мне ограничить разрешения моей конфигурации только определенными членами моей команды.

Настройка базы данных

Поскольку мы собираемся отправлять токены сброса пользователям, нам нужно хранить эти токены в базе данных.

Я предполагаю, что у вас есть действующая таблица пользователей в вашей базе данных. Если вы уже используете Sequelize, отлично! Если нет, вы можете освежить в памяти Sequelize и интерфейс командной строки Sequelize.

Если вы еще не использовали Sequelize в своем приложении, вы можете настроить его, выполнив приведенную ниже команду в корневой папке вашего приложения:

$ sequelize init

Это создаст несколько новых папок в вашей настройке, включая миграции и модели.

Это также создаст файл конфигурации. В файле конфигурации обновите development block с учетными данными для вашего локального сервера базы данных mysql.

Давайте воспользуемся инструментом командной строки Sequelize, чтобы сгенерировать для нас таблицу базы данных.

$ sequelize model:create --name ResetToken --attributes email:string,token:string,expiration:date,used:integer
$ sequelize db:migrate

Эта таблица имеет следующие столбцы:

  • адрес электронной почты пользователя,
  • Токен, который был сгенерирован,
  • Срок действия этого токена,
  • Был ли токен использован или нет.

В фоновом режиме sequenceize-cli выполняет следующий SQL-запрос:

CREATE TABLE `ResetTokens` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `email` varchar(255) DEFAULT NULL,
  `token` varchar(255) DEFAULT NULL,
  `expiration` datetime DEFAULT NULL,
  `createdAt` datetime NOT NULL,
  `updatedAt` datetime NOT NULL,
  `used` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

Убедитесь, что это работает правильно, используя клиент SQL или командную строку:

mysql> describe ResetTokens;
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | int(11)      | NO   | PRI | NULL    | auto_increment |
| email      | varchar(255) | YES  |     | NULL    |                |
| token      | varchar(255) | YES  |     | NULL    |                |
| expiration | datetime     | YES  |     | NULL    |                |
| createdAt  | datetime     | NO   |     | NULL    |                |
| updatedAt  | datetime     | NO   |     | NULL    |                |
| used       | int(11)      | NO   |     | 0       |                |
+------------+--------------+------+-----+---------+----------------+
7 rows in set (0.00 sec)

Совет по безопасности №3

Если вы в настоящее время не используете ORM, вам следует подумать об этом. ORM автоматизирует написание и правильное экранирование SQL-запросов, делая ваш код более читабельным и безопасным по умолчанию. Они помогут вам избежать атак SQL-инъекций, правильно избегая ваших SQL-запросов.

Настройка маршрута сброса пароля

Создайте маршрут получения в user.js:

router.get('/ot-password', function(req, res, next) {
  res.render('user/ot-password', { });
});

Затем создайте маршрут POST, который будет использоваться при публикации формы сброса пароля. В приведенном ниже коде я включил несколько важных функций безопасности.

Советы по безопасности № 4–6

  1. Даже если мы не находим адрес электронной почты, мы возвращаем статус «ok». Мы не хотим, чтобы недобросовестные боты выясняли, какие электронные письма в нашей базе данных настоящие, а какие нет.
  2. Чем больше случайных байтов вы используете в токене, тем меньше вероятность его взлома. Мы используем 64 случайных байта в нашем генераторе токенов (не используйте меньше 8).
  3. Срок действия токена истекает через 1 час. Это ограничивает окно времени, в течение которого работает токен сброса.
router.post('/ot-password', async function(req, res, next) {
  //ensure that you have a user with this email
  var email = await User.findOne({where: { email: req.body.email }});
  if (email == null) {
  /**
   * we don't want to tell attackers that an
   * email doesn't exist, because that will let
   * them use this form to find ones that do
   * exist.
   **/
    return res.json({status: 'ok'});
  }
  /**
   * Expire any tokens that were previously
   * set for this user. That prevents old tokens
   * from being used.
   **/
  await ResetToken.update({
      used: 1
    },
    {
      where: {
        email: req.body.email
      }
  });
 
  //Create a random reset token
  var fpSalt = crypto.randomBytes(64).toString('base64');
 
  //token expires after one hour
  var expireDate = new Date(new Date().getTime() + (60 * 60 * 1000))
 
  //insert token data into DB
  await ResetToken.create({
    email: req.body.email,
    expiration: expireDate,
    token: fpSalt,
    used: 0
  });
 
  //create email
  const message = {
      from: process.env.SENDER_ADDRESS,
      to: req.body.email,
      replyTo: process.env.REPLYTO_ADDRESS,
      subject: process.env.FORGOT_PASS_SUBJECT_LINE,
      text: 'To reset your password, please click the link below.nnhttps://'+process.env.DOMAIN+'/user/reset-password?token='+encodeURIComponent(token)+'&email="+req.body.email
  };

  //send email
  transport.sendMail(message, function (err, info) {
     if(err) { console.log(err)}
     else { console.log(info); }
  });
 
  return res.json({status: "ok'});
});

Вы увидите указанную выше пользовательскую переменную — что это? Для целей этого руководства мы предполагаем, что у вас есть модель пользователя, которая подключается к вашей базе данных для получения значений. Приведенный выше код основан на Sequelize, но вы можете изменить его по мере необходимости, если будете запрашивать базу данных напрямую (но я рекомендую Sequelize!).

Теперь нам нужно сгенерировать представление. При использовании Bootstrap CSS, jQuery и фреймворка pug, встроенного в фреймворк Node Express, представление выглядит следующим образом:

extends ../layout
 
block content
  div.container
    div.row
      div.col
        h1 ot password
        p Enter your email address below. If we have it on file, we will send you a reset email.
        div.ot-message.alert.alert-success(style="display:none;") Email address received. If you have an email on file we will send you a reset email. Please wait a few minutes and check your spam folder if you don't see it.
        form#otPasswordForm.form-inline(onsubmit="return false;")
          div.form-group
            label.sr-only(for="email") Email address:
            input.form-control.mr-2#emailFp(type="email", name="email", placeholder="Email address")
          div.form-group.mt-1.text-center
            button#fpButton.btn.btn-success.mb-2(type="submit") Send email
 
  script.
    $('#fpButton').on('click', function() {
      $.post('/user/ot-password', {
        email: $('#emailFp').val(),
      }, function(resp) {
        $('.ot-message').show();
        $('#otPasswordForm').remove();
      });
    });

Вот форма на странице:

поле сброса пароля для безопасного рабочего процесса сброса пароля

Форма сброса пароля. (Большой превью)

На этом этапе вы должны быть в состоянии заполнить форму с адресом электронной почты, который находится в вашей базе данных, а затем получить электронное письмо для сброса пароля на этот адрес. Нажатие на ссылку сброса пока ничего не даст.

Настройте маршрут «Сброс пароля»

Теперь давайте продолжим и настроим остальную часть рабочего процесса.

Добавьте модуль Sequelize.Op к вашему маршруту:

const Sequelize = require('sequelize');
const Op = Sequelize.Op;

Теперь давайте построим маршрут GET для пользователей, которые щелкнули ссылку для сброса пароля. Как вы увидите ниже, мы хотим убедиться, что правильно проверяем токен сброса.

Совет по безопасности № 7:

Убедитесь, что вы ищете только те токены сброса, срок действия которых не истек и которые не использовались.

В демонстрационных целях я также очищаю здесь все токены с истекшим сроком действия при загрузке, чтобы таблица оставалась небольшой. Если у вас большой веб-сайт, перенесите его на cronjob.

router.get('/reset-password', async function(req, res, next) {
  /**
   * This code clears all expired tokens. You
   * should move this to a cronjob if you have a
   * big site. We just include this in here as a
   * demonstration.
   **/
  await ResetToken.destroy({
    where: {
      expiration: { [Op.lt]: Sequelize.fn('CURDATE')},
    }
  });
 
  //find the token
  var record = await ResetToken.findOne({
    where: {
      email: req.query.email,
      expiration: { [Op.gt]: Sequelize.fn('CURDATE')},
      token: req.query.token,
      used: 0
    }
  });
 
  if (record == null) {
    return res.render('user/reset-password', {
      message: 'Token has expired. Please try password reset again.',
      showForm: false
    });
  }
 
  res.render('user/reset-password', {
    showForm: true,
    record: record
  });
});

Теперь давайте создадим маршрут POST, который будет активирован, когда пользователь введет свои новые данные пароля.

Совет по безопасности с 8 по 11:

  • Убедитесь, что пароли совпадают и соответствуют вашим минимальным требованиям.
  • Еще раз проверьте токен сброса, чтобы убедиться, что он не использовался и срок его действия не истек. Нам нужно проверить это снова, потому что токен отправляется пользователем через форму.
  • Перед сбросом пароля пометьте токен как использованный. Таким образом, если произойдет что-то непредвиденное (например, сбой сервера), пароль не будет сброшен, пока токен все еще действителен.
  • Используйте криптографически безопасную случайную соль (в данном случае мы используем 64 случайных байта).
router.post('/reset-password', async function(req, res, next) {
  //compare passwords
  if (req.body.password1 !== req.body.password2) {
    return res.json({status: 'error', message: 'Passwords do not match. Please try again.'});
  }
 
  /**
  * Ensure password is valid (isValidPassword
  * function checks if password is >= 8 chars, alphanumeric,
  * has special chars, etc)
  **/
  if (!isValidPassword(req.body.password1)) {
    return res.json({status: 'error', message: 'Password does not meet minimum requirements. Please try again.'});
  }
 
  var record = await ResetToken.findOne({
    where: {
      email: req.body.email,
      expiration: { [Op.gt]: Sequelize.fn('CURDATE')},
      token: req.body.token,
      used: 0
    }
  });
 
  if (record == null) {
    return res.json({status: 'error', message: 'Token not found. Please try the reset password process again.'});
  }
 
  var upd = await ResetToken.update({
      used: 1
    },
    {
      where: {
        email: req.body.email
      }
  });
 
  var newSalt = crypto.randomBytes(64).toString('hex');
  var newPassword = crypto.pbkdf2Sync(req.body.password1, newSalt, 10000, 64, 'sha512').toString('base64');
 
  await User.update({
    password: newPassword,
    salt: newSalt
  },
  {
    where: {
      email: req.body.email
    }
  });
 
  return res.json({status: 'ok', message: 'Password reset. Please login with your new password.'});
});

And again, the view:

extends ../layout
 
block content
  div.container
    div.row
      div.col
        h1 Reset password
        p Enter your new password below.
        if message
          div.reset-message.alert.alert-warning #{message}
        else
          div.reset-message.alert(style="display:none;")
        if showForm
          form#resetPasswordForm(onsubmit="return false;")
            div.form-group
              label(for="password1") New password:
              input.form-control#password1(type="password", name="password1")
              small.form-text.text-muted Password must be 8 characters or more.
            div.form-group
              label(for="password2") Confirm new password
              input.form-control#password2(type="password", name="password2")
              small.form-text.text-muted Both passwords must match.
            input#emailRp(type="hidden", name="email", value=record.email)
            input#tokenRp(type="hidden", name="token", value=record.token)
            div.form-group
              button#rpButton.btn.btn-success(type="submit") Reset password
 
  script.
    $('#rpButton').on('click', function() {
      $.post('/user/reset-password', {
        password1: $('#password1').val(),
        password2: $('#password2').val(),
        email: $('#emailRp').val(),
        token: $('#tokenRp').val()
      }, function(resp) {
        if (resp.status == 'ok') {
          $('.reset-message').removeClass('alert-danger').addClass('alert-success').show().text(resp.message);
          $('#resetPasswordForm').remove();
        } else {
          $('.reset-message').removeClass('alert-success').addClass('alert-danger').show().text(resp.message);
        }
      });
    });

Вот как это должно выглядеть:

форма сброса пароля для безопасного рабочего процесса сброса пароля

Форма сброса пароля. (Большой превью)

Наконец, не забудьте добавить ссылку на этот поток со страницы входа! Как только вы это сделаете, у вас должен быть работающий поток сброса пароля. Обязательно тщательно тестируйте на каждом этапе процесса, чтобы убедиться, что все работает, а ваши токены имеют короткий срок действия и помечаются правильным статусом по мере продвижения рабочего процесса.

Следующие шаги

Надеюсь, это помогло вам создать безопасную и удобную функцию сброса пароля.

  • Если вам интересно узнать больше о криптографической безопасности, я рекомендую краткую информацию из Википедии (осторожно, она слишком объемная!).
  • Если вы хотите повысить безопасность аутентификации вашего приложения, обратите внимание на 2FA. Есть много разных вариантов.
  • Если я отпугнул вас от создания собственного процесса сброса пароля, вы можете положиться на сторонние системы входа, такие как Google и Facebook. PassportJS — это промежуточное ПО, которое вы можете использовать для NodeJS, реализующее эти стратегии.
Сокрушительная редакция
(дм, ук, иль)



[ad_2]
Source: https://smashingmagazine.com

Заключение

Вы ознакомились с статьей — Создание безопасных потоков паролей с помощью NodeJS и MySQL

Пожалуйста оцените статью, и напишите комментарий.

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован.

Краткое описание по статье Создание безопасных потоков паролей с помощью NodeJS и MySQL

Название: Создание безопасных потоков паролей с помощью NodeJS и MySQL . Краткое описание: [ad_1] ⭐ Даршан . Дата публикации: 21.01.2022 . Автор: Алишер Валеев .

Для чего создан сайт Novosti-Nedeli.ru

Данный сайт посвящен новостям мира и мира технологий . Также тут вы найдете руководства по различным девайсам.

Сколько лет сайту?

Возраст составляет 3 года

Кнопка «Наверх»