Tạo mật khẩu an toàn với NodeJS và MySQL – Tạp chí Smashing

[ad_1]

Giới thiệu về tác giả

Darshan Somashekar là một doanh nhân công nghệ khởi nghiệp. Dự án thú vị mới nhất của anh là một trang web solitaire có tên Solitaired. Trước đây, đào
Thông tin thêm về
Darshan
Hoài

Đặt lại chức năng mật khẩu là cổ phần bảng cho bất kỳ ứng dụng thân thiện với người dùng nào. Nó cũng có thể là một cơn ác mộng an ninh. Sử dụng NodeJS và MySQL, Darshan trình bày cách tạo thành công luồng mật khẩu đặt lại an toàn để bạn có thể tránh những cạm bẫy này.

Nếu bạn bất cứ điều gì giống tôi, bạn đã quên mật khẩu của mình hơn một lần, đặc biệt là trên các trang web mà bạn đã ghé thăm trong một thời gian. Bạn có thể cũng đã nhìn thấy và / hoặc bị thế chấp bằng cách đặt lại email mật khẩu có chứa mật khẩu của bạn ở dạng văn bản đơn giản.

Thật không may, quy trình đặt lại mật khẩu bị thu hẹp và hạn chế sự chú ý trong quá trình phát triển ứng dụng. Điều này không chỉ có thể dẫn đến trải nghiệm người dùng bực bội mà còn có thể khiến ứng dụng của bạn bị lỗ hổng bảo mật.

Chúng tôi sẽ hướng dẫn cách xây dựng quy trình đặt lại mật khẩu an toàn. Chúng tôi sẽ sử dụng NodeJS và MySQL làm thành phần cơ bản. Nếu bạn đang viết bằng ngôn ngữ, khung hoặc cơ sở dữ liệu khác nhau, bạn vẫn có thể hưởng lợi từ việc làm theo "Mẹo bảo mật" chung được nêu trong mỗi phần.

Luồng mật khẩu đặt lại bao gồm các thành phần sau:

  • Một liên kết để gửi người dùng đến khi bắt đầu quy trình làm việc.
  • Một hình thức cho phép người dùng gửi email của họ.
  • Một tra cứu xác thực email và gửi một email đến địa chỉ.
  • Một email chứa mã thông báo đặt lại có thời hạn cho phép người dùng đặt lại mật khẩu của họ.
  • Một hình thức cho phép người dùng tạo mật khẩu mới.
  • Lưu mật khẩu mới và cho phép người dùng đăng nhập lại bằng mật khẩu mới.

Bên cạnh Node, Express & MySQL, chúng tôi sẽ sử dụng các thư viện sau:

Sequelize là một ORM cơ sở dữ liệu NodeJS giúp cho việc chạy di chuyển cơ sở dữ liệu cũng như bảo mật tạo q dễ dàng hơn ueries. Nodemailer là thư viện email NodeJS phổ biến mà chúng tôi sẽ sử dụng để gửi email đặt lại mật khẩu.

Mẹo bảo mật # 1

Một số bài viết đề xuất luồng mật khẩu an toàn có thể được thiết kế bằng JSON Web Tokens (JWT) , loại bỏ nhu cầu lưu trữ cơ sở dữ liệu (và do đó dễ thực hiện hơn). Chúng tôi không sử dụng phương pháp này trên trang web của mình, vì các bí mật mã thông báo JWT thường được lưu trữ ngay trong mã. Chúng tôi muốn tránh có 'một bí mật' để thống trị tất cả (vì cùng lý do bạn không sử dụng mật khẩu muối có cùng giá trị), và do đó cần chuyển thông tin này vào cơ sở dữ liệu.

Cài đặt

nơi bạn muốn bao gồm các quy trình công việc thiết lập lại của mình, hãy thêm các mô-đun cần thiết. Nếu bạn cần xem lại trên Express và các tuyến đường, hãy xem hướng dẫn của họ.

  const gậtemailer = Yêu cầu (' thông tin đăng nhập. 

  const Transport = gậtemailer.createTransport ({
    máy chủ lưu trữ: process.env.EMAIL_HOST,
    cổng: process.env.EMAIL_PORT,
    an toàn: đúng,
    xác thực: {
       người dùng: process.env.EMAIL_USER,
       vượt qua: process.env.EMAIL_PASS
    }
});  

Giải pháp email tôi đang sử dụng là Dịch vụ email đơn giản của AWS, nhưng bạn có thể sử dụng mọi thứ (Mailgun, v.v.).

Nếu đây là lần đầu tiên bạn thiết lập dịch vụ gửi email của bạn, bạn sẽ cần dành thời gian để định cấu hình Khóa miền thích hợp và thiết lập ủy quyền. Nếu bạn sử dụng Tuyến 53 cùng với SES, việc này cực kỳ đơn giản và được thực hiện hầu như tự động, đó là lý do tại sao tôi chọn nó. AWS có một số hướng dẫn về cách SES hoạt động với Route53.

Mẹo bảo mật # 2

Để lưu thông tin đăng nhập khỏi mã của tôi, tôi sử dụng dotenv, cho phép tôi tạo tệp .env cục bộ với biến môi trường của tôi. Bằng cách đó, khi tôi triển khai vào sản xuất, tôi có thể sử dụng các khóa sản xuất khác nhau không thể nhìn thấy trong mã và do đó cho phép tôi giới hạn quyền của cấu hình đối với chỉ một số thành viên trong nhóm của tôi.

Thiết lập cơ sở dữ liệu

Vì chúng tôi sẽ gửi mã thông báo đặt lại cho người dùng, chúng tôi cần lưu trữ các mã thông báo đó trong cơ sở dữ liệu.

Tôi giả sử bạn có bảng người dùng hoạt động trong cơ sở dữ liệu của mình. Nếu bạn đã sử dụng Sequelize rồi, thật tuyệt! Nếu không, bạn có thể muốn cải tiến Sequelize và Sequelize CLI.

Nếu bạn chưa sử dụng Sequelize trong ứng dụng của mình, bạn có thể thiết lập nó bằng cách chạy lệnh bên dưới trong thư mục gốc của ứng dụng:

  $ sequelize init  

Điều này sẽ tạo ra một số thư mục mới trong thiết lập của bạn, bao gồm di chuyển và mô hình.

Điều này cũng sẽ tạo ra một cấu hình tập tin. Trong tệp cấu hình của bạn, hãy cập nhật khối phát triển với thông tin đăng nhập vào máy chủ cơ sở dữ liệu mysql cục bộ của bạn.

Hãy sử dụng công cụ CLI của Sequelize để tạo bảng cơ sở dữ liệu cho chúng tôi.

  $ mô hình tuần tự hóa: tạo --name ResetToken - phân phối email: chuỗi, mã thông báo: chuỗi, hết hạn: ngày, được sử dụng: số nguyên
$ sequelize db: di chuyển  

Bảng này có các cột sau:

  • Địa chỉ email của người dùng,
  • Mã thông báo đã được tạo,
  • Hết hạn mã thông báo đó,
  • Liệu mã thông báo đã được sử dụng hay chưa.

Trong nền, trình tự tiếp theo đang chạy truy vấn SQL sau:

  TẠO BẢNG `ResetTokens` (
  `id` int (11) KHÔNG NULL AUTO_INCREMENT,
  `email` varchar (255) DEFAULT NULL,
  `token` varchar (255) DEFAULT NULL,
  `hết hạn` datetime DEFAULT NULL,
  `createdAt` datetime KHÔNG NULL,
  `updateAt` datetime KHÔNG NULL,
  `used` int (11) KHÔNG NULL DEFAULT '0',
  KHÓA CHÍNH (`id`)
) ĐỘNG CƠ = InnoDB TỰ ĐỘNG TỰ ĐỘNG  mysql> mô tả ResetTokens;
+ ------------ + -------------- + ------ + ----- + -------- - + ---------------- +
| Lĩnh vực | Loại | Không | Chìa khóa | Mặc định | Thêm |
+ ------------ + -------------- + ------ + ----- + -------- - + ---------------- +
| id | int (11) | KHÔNG | PRI | NULL | auto_increment |
| email | varchar (255) | CÓ | | NULL | |
| mã thông báo | varchar (255) | CÓ | | NULL | |
| hết hạn | thời gian | CÓ | | NULL | |
| đã tạoAt | thời gian | KHÔNG | | NULL | |
| cập nhậtAt | thời gian | KHÔNG | | NULL | |
| đã sử dụng | int (11) | KHÔNG | | 0 | |
+ ------------ + -------------- + ------ + ----- + -------- - + ---------------- +
7 hàng trong bộ (0,00 giây)  

Mẹo bảo mật # 3

Nếu bạn hiện không sử dụng ORM, bạn nên cân nhắc làm như vậy. Một ORM tự động hóa việc viết và thoát đúng các truy vấn SQL, làm cho mã của bạn dễ đọc hơn và an toàn hơn theo mặc định. Họ sẽ giúp bạn tránh các cuộc tấn công tiêm nhiễm SQL bằng cách thoát đúng các truy vấn SQL của bạn.

Thiết lập lại mật khẩu tuyến đường

Tạo tuyến đường lấy trong user.js :

  router.get ('/ quên mật khẩu', chức năng (req, res, next) {
  res.render ('người dùng / quên mật khẩu', {});
});  

Sau đó, tạo tuyến đường POST, đây là tuyến đường được nhấn khi biểu mẫu đặt lại mật khẩu được đăng. Trong mã bên dưới, tôi đã bao gồm một số tính năng bảo mật quan trọng.

Mẹo bảo mật # 4-6

  1. Ngay cả khi chúng tôi không tìm thấy địa chỉ email, chúng tôi sẽ trả lại 'ok' như trạng thái của chúng tôi. Chúng tôi không muốn các bot không mong muốn tìm ra email nào là thật so với không có thật trong cơ sở dữ liệu của chúng tôi.
  2. Càng sử dụng nhiều byte ngẫu nhiên trong mã thông báo, càng ít có khả năng bị hack. Chúng tôi đang sử dụng 64 byte ngẫu nhiên trong trình tạo mã thông báo của chúng tôi (không sử dụng ít hơn 8).
  3. Hết hạn mã thông báo sau 1 giờ. Điều này giới hạn cửa sổ thời gian mã thông báo đặt lại hoạt động.
  router.post ('/ quên mật khẩu', chức năng async (req, res, next) {
  // đảm bảo rằng bạn có người dùng với email này
  var email = await User.findOne ({trong đó: {email: req.body.email}});
  if (email == null) {
  / **
   * chúng tôi không muốn nói với những kẻ tấn công rằng
   * email không tồn tại, vì điều đó sẽ cho phép
   * họ sử dụng mẫu này để tìm những người làm
   * hiện hữu.
   ** /
    return res.json ({status: 'ok'});
  }
  / **
   * Hết hạn bất kỳ mã thông báo nào trước đây
   * đặt cho người dùng này. Điều đó ngăn chặn các mã thông báo cũ
   * từ đang được sử dụng.
   ** /
  đang chờ ResetToken.update ({
      đã sử dụng: 1
    },
    {
      Ở đâu: {
        email: req.body.email
      }
  });
 
  // Tạo mã thông báo đặt lại ngẫu nhiên
  var fpSalt = crypto.randomBytes (64) .toString ('base64');
 
  // mã thông báo hết hạn sau một giờ
  var expireDate = ngày mới ();
  expireDate.setDate (expireDate.getDate () + 1/24);
 
  // chèn dữ liệu mã thông báo vào DB
  đang chờ ResetToken.create ({
    email: req.body.email,
    hết hạn: hết hạn,
    mã thông báo: mã thông báo,
    đã sử dụng: 0
  });
 
  // tạo email
  tin nhắn const = {
      từ: process.env.SENDER_ADDRESS,
      đến: req.body.email,
      trả lời: process.env.REPLYTO_ADDRESS,
      chủ đề: process.env.FORGOT_PASS_SUBJECT_LINE,
      văn bản: 'Để đặt lại mật khẩu của bạn, vui lòng nhấp vào liên kết bên dưới.  n  nhttps: //'+ Process.env.DOMAIN+'/user/reset-password? token =' + encodeURIComponent (mã thông báo) + '& email =' + req.body.email
  };
 
  //gửi email
  Transport.sendMail (tin nhắn, chức năng (err, thông tin) {
     if (err) {console.log (err)}
     other {console.log (thông tin); }
  });
 
  return res.json ({status: 'ok'});
});  

Bạn sẽ thấy một biến Người dùng được tham chiếu ở trên - đây là gì? Với mục đích của hướng dẫn này, chúng tôi cho rằng bạn có mô hình Người dùng kết nối với cơ sở dữ liệu của bạn để truy xuất các giá trị. Mã ở trên dựa trên Sequelize, nhưng bạn có thể sửa đổi khi cần nếu bạn truy vấn trực tiếp cơ sở dữ liệu (nhưng tôi khuyên bạn nên Sequelize!).

Bây giờ chúng ta cần tạo chế độ xem. Sử dụng Bootstrap CSS, jQuery và khung pug được tích hợp trong khung Node Express, chế độ xem trông như sau:

  mở rộng ../layout
 
khối nội dung
  div.container
    div.row
      div.col
        h1 Quên mật khẩu
        p Nhập địa chỉ email của bạn dưới đây. Nếu chúng tôi có nó trong hồ sơ, chúng tôi sẽ gửi cho bạn một email đặt lại.
        div.forgot-message.alert.alert-thành công (style = "display: none;") Địa chỉ email nhận được. Nếu bạn có một email trong hồ sơ, chúng tôi sẽ gửi cho bạn một email thiết lập lại. Vui lòng đợi vài phút và kiểm tra thư mục thư rác nếu bạn không thấy nó.
        biểu mẫu # quênPasswordForm.form-inline (onsubmit = "return false;")
          nhóm div.form
            nhãn.sr-only (for = "email") Địa chỉ email:
            input.form-control.mr-2 # emailFp (type = 'email', name = 'email', placeholder = "Địa chỉ email")
          div.form-group.mt-1.text-centre
            nút # fpButton.btn.btn-thành công.mb-2 (loại = 'gửi') Gửi email
 
  kịch bản.
    $ ('# fpButton'). on ('click', function () {
      $ .post ('/ người dùng / quên mật khẩu', {
        email: $ ('# emailFp'). val (),
      }, hàm (resp) {
        $ ('. quên tin nhắn'). show ();
        $ ('# quênPasswordForm'). remove ();
      });
    });  

Đây là mẫu trên trang:

 đặt lại trường mật khẩu cho quy trình đặt lại mật khẩu an toàn của bạn
. (Xem trước lớn)

Tại thời điểm này, bạn sẽ có thể điền vào biểu mẫu với một địa chỉ email có trong cơ sở dữ liệu của bạn và sau đó nhận email mật khẩu đặt lại tại địa chỉ đó. Nhấp vào liên kết đặt lại sẽ không làm gì cả.

Thiết lập Cài đặt lại mật khẩu Tuyến đường

Bây giờ, hãy tiếp tục và thiết lập phần còn lại của quy trình làm việc.

Thêm mô-đun Sequelize.Op theo lộ trình của bạn:

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

Bây giờ hãy xây dựng tuyến GET cho người dùng đã nhấp vào liên kết đặt lại mật khẩu đó. Như bạn sẽ thấy bên dưới, chúng tôi muốn đảm bảo rằng chúng tôi xác thực mã thông báo đặt lại một cách thích hợp.

Mẹo bảo mật # 7:

Đảm bảo bạn chỉ tìm kiếm mã thông báo đặt lại mà không đã hết hạn và chưa được sử dụng.

Với mục đích trình diễn, tôi cũng dọn sạch tất cả các mã thông báo đã hết hạn khi tải ở đây để giữ cho bảng nhỏ. Nếu bạn có một trang web lớn, hãy chuyển trang này sang cronjob.

  router.get ('/ reset-password', chức năng async (req, res, next) {
  / **
   * Mã này xóa tất cả các mã thông báo đã hết hạn. Bạn
   * nên chuyển cái này sang cronjob nếu bạn có
   * trang web lớn. Chúng tôi chỉ bao gồm điều này ở đây như là một
   * trình diễn.
   ** /
  đang chờ ResetToken.destroy ({
    Ở đâu: {
      hết hạn: {[Op.lt]: Sequelize.fn ('HIỆN TẠI')},
    }
  });
 
  // tìm mã thông báo
  var record = đang chờ ResetToken.findOne ({
    Ở đâu: {
      email: req.query.email,
      hết hạn: {[Op.gt]: Sequelize.fn ('HIỆN TẠI')},
      mã thông báo: req.query.token,
      đã sử dụng: 0
    }
  });
 
  if (record == null) {
    trả lại res.render ('người dùng / đặt lại mật khẩu', {
      tin nhắn: 'Mã thông báo đã hết hạn. Vui lòng thử đặt lại mật khẩu. ',
      showForm: sai
    });
  }
 
  res.render ('người dùng / đặt lại mật khẩu', {
    showForm: đúng,
    hồ sơ: hồ sơ
  });
});  

Bây giờ, hãy tạo tuyến đường POST bị tấn công khi người dùng điền chi tiết mật khẩu mới của họ.

Mẹo bảo mật # 8 đến 11:

  • Đảm bảo rằng mật khẩu khớp và đáp ứng các yêu cầu tối thiểu của bạn.
  • Kiểm tra lại mã thông báo đặt lại để đảm bảo rằng nó chưa được sử dụng và chưa hết hạn. Chúng tôi cần kiểm tra lại vì mã thông báo đang được gửi bởi người dùng thông qua biểu mẫu.
  • Trước khi đặt lại mật khẩu, hãy đánh dấu mã thông báo là đã sử dụng. Theo cách đó, nếu có điều gì đó không lường trước được xảy ra (ví dụ như sự cố máy chủ), mật khẩu sẽ không được đặt lại trong khi mã thông báo vẫn còn hiệu lực.
  • Sử dụng muối ngẫu nhiên được bảo mật bằng mật mã (trong trường hợp này, chúng tôi sử dụng 64 ngẫu nhiên byte).
  router.post ('/ reset-password', chức năng async (req, res, next) {
  // so sánh mật khẩu
  if (req.body.password1! == req.body.password2) {
    return res.json ({status: 'error', message: 'Mật khẩu không khớp. Vui lòng thử lại.'});
  }
 
  / **
  * Đảm bảo mật khẩu hợp lệ (isValidPassword
  * chức năng kiểm tra nếu mật khẩu> = 8 ký tự, chữ và số,
  * có ký tự đặc biệt, v.v.)
  ** /
  if (! isValidPassword (req.body.password1)) {
    return res.json ({status: 'error', message: 'Mật khẩu không đáp ứng các yêu cầu tối thiểu. Vui lòng thử lại.'});
  }
 
  var record = đang chờ ResetToken.findOne ({
    Ở đâu: {
      email: req.body.email,
      hết hạn: {[Op.gt]: Sequelize.fn ('HIỆN TẠI')},
      mã thông báo: req.body.token,
      đã sử dụng: 0
    }
  });
 
  if (record == null) {
    return res.json ({status: 'error', message: 'Không tìm thấy mã thông báo. Vui lòng thử lại quy trình đặt lại mật khẩu.'});
  }
 
  var upd = đang chờ ResetToken.update ({
      đã sử dụng: 1
    },
    {
      Ở đâu: {
        email: req.body.email
      }
  });
 
  var newSalt = crypto.randomBytes (64) .toString ('hex');
  var newPassword = crypto.pbkdf2Sync (req.body.password1, newSalt, 10000, 64, 'sha512'). toString ('base64');
 
  đang chờ User.update ({
    mật khẩu: newPassword,
    muối: newSalt
  },
  {
    Ở đâu: {
      email: req.body.email
    }
  });
 
  return res.json ({status: 'ok', tin nhắn: 'Đặt lại mật khẩu. Vui lòng đăng nhập bằng mật khẩu mới của bạn.'});
});

Và một lần nữa, quan điểm:

kéo dài ../layout
 
khối nội dung
  div.container
    div.row
      div.col
        h1 Đặt lại mật khẩu
        p Nhập mật khẩu mới của bạn dưới đây.
        nếu nhắn tin
          div.reset-message.alert.alert-warning # {message}
        khác
          div.reset-message.alert (style = 'display: none;')
        nếu hiển thị
          biểu mẫu # resetPasswordForm (onsubmit = "return false;")
            nhóm div.form
              nhãn (for = "password1") Mật khẩu mới:
              input.form-control # password1 (type = 'password', name = 'password1')
              Mật khẩu nhỏ.form-text.text-tắt phải có 8 ký tự trở lên.
            nhóm div.form
              nhãn (for = "password2") Xác nhận mật khẩu mới
              input.form-control # password2 (type = 'password', name = 'password2')
              small.form-text.text-tắt Cả hai mật khẩu phải khớp.
            đầu vào # emailRp (type = 'hidden', name = 'email', value = record.email)
            đầu vào # tokenRp (type = 'hidden', name = 'token', value = record.token)
            nhóm div.form
              nút # rpButton.btn.btn-thành công (loại = 'gửi') Đặt lại mật khẩu
 
  kịch bản.
    $ ('# rpButton'). on ('click', function () {
      $ .post ('/ người dùng / đặt lại mật khẩu', {
        mật khẩu1: $ ('# password1'). val (),
        password2: $ ('# password2'). val (),
        email: $ ('# emailRp'). val (),
        mã thông báo: $ ('# tokenRp'). val ()
      }, hàm (resp) {
        if (resp.status == 'ok') {
          $ ('. reset-message'). removeClass ('alert-risk'). addClass ('alert-thành công'). show (). text (resp.message);
          $ ('# resetPasswordForm'). remove ();
        } khác {
          $ ('. reset-message'). removeClass ('alert-thành công'). addClass ('alert-risk'). show (). text (resp.message);
        }
      });
    });  

Đây là giao diện của nó:

 đặt lại mẫu mật khẩu cho quy trình đặt lại mật khẩu an toàn của bạn
hình thức. (Bản xem trước lớn)

Thêm liên kết vào trang đăng nhập của bạn

Cuối cùng, đừng quên thêm một liên kết đến luồng này từ trang đăng nhập của bạn! Khi bạn thực hiện việc này, bạn sẽ có một luồng mật khẩu thiết lập lại hoạt động. Hãy chắc chắn kiểm tra kỹ lưỡng ở từng giai đoạn của quy trình để xác nhận mọi thứ hoạt động và mã thông báo của bạn đã hết hạn ngắn và được đánh dấu với trạng thái chính xác khi tiến trình công việc tiến triển.

Các bước tiếp theo

Hy vọng điều này đã giúp bạn trên con đường mã hóa tính năng đặt lại mật khẩu an toàn, thân thiện với người dùng.

  • Nếu bạn muốn tìm hiểu thêm về bảo mật mật mã, tôi khuyên bạn nên tóm tắt Wikipedia (cảnh báo, nó rất dày đặc!).
  • Nếu bạn muốn thêm bảo mật hơn nữa cho xác thực ứng dụng của mình, hãy xem xét 2FA. Có rất nhiều tùy chọn khác nhau ngoài kia.
  • Nếu I Hồive sợ bạn xây dựng luồng mật khẩu đặt lại của riêng bạn, bạn có thể dựa vào các hệ thống đăng nhập của bên thứ ba như Google và Facebook. PassportJS là một phần mềm trung gian mà bạn có thể sử dụng cho NodeJS thực hiện các chiến lược này.
 Smashing Editorial
(dm, yk, il)

[ad_2]
Source link: webdesignernews

Leave a Reply

Your email address will not be published. Required fields are marked *