Node.js
セキュリティ
bcrypt
sequelize

Sequelize + bcrypt でパスワード認証を実装する

More than 1 year has passed since last update.

Sequelize

node.jsのためのORM。RubyのActiveRecordに相当するもの。
他にもbookshelfなどがある。

$ npm i sequelize --save

bcrypt?

bcryptはBlowfishという暗号化アルゴリズムの実装。
ハッシュ化の必要性やSHAファミリなどのハッシュ化ではなぜ駄目なのかについてはこの辺を参照。

node.jsのbcrypt実装はネイティブモジュールでコンパイラのバージョンが古いとビルドできないことがあるので注意。g++4.8以上ならビルドできることを確認している。clangでも7.0.0以降ならまず大丈夫。

$ npm i bcrypt --save

https://github.com/ncb000gt/node.bcrypt.js/

モデル作成

まずモデルを作る。RailsのActiveRecordほど使いやすくはないが一応Sequelizeにもマイグレーションツールがある。

$ npm i sequelize-cli -D

Userモデルをサクッと作る。

$ ./node_modules/.bin/sequelize model:create \
--name User \
--attributes 'name:string, email:string, password_hash:string'

パスワードのスキーマ

生成されたUserモデルでpasswordの属性を追加する。
ここでDataTypes.VIRTUALpasswordはcreateやupdateの際に値を引数に受け取るが、実際にデータベースには保存されない属性になる。
この後の実装で代わりにハッシュ化したものをpassword_hashに保存する。

password_hash: {
  type: DataTypes.STRING,
},
password: {
  type: DataTypes.VIRTUAL,
  validate: {
    min: 8,
    max: 32,
  },
},

パスワードのハッシュ化

bcrypt.hash()を使う。bcrypt.hashSync()は同期メソッドなので使わない。
createやupdateの前にフックする関数を作り、ここでバーチャル属性のpasswordに入っている値をbcryptでハッシュ化してpassword_hashに突っ込む。

var hashPasswordHook = function(user, options, callback) {

  bcrypt.hash(user.get('password'), 10, function(err, hash) {
    if (err) {
      return callback(err);
    }

    user.set('password_hash', hash);
    return callback(null, options);
  });

};

パスワードの認証メソッド

bcrypt.compare()を使う。bcrypt.compareSync()は同期メソッドなので使わない。
instanceMethodsauthenticateとか適当な名前で生やす。また、先ほどのハッシュ化関数をcreateとupdateの前に走らせるため、hooksに指定しておく。

var User = sequelize.define('User',
{
  //attributes...
},
{
  hooks: {
    beforeCreate: hashPasswordHook,
    beforeUpdate: hashPasswordHook,
  },
  instanceMethods: {
    authenticate: function(password, callback) {
      bcrypt.compare(password, this.password_hash, function(err, isValid) {
        if (err) {
          return callback(err);
        } else {
          return callback(null, isValid);
        }
      });
    },
  },
})

認証の実装

expressの実装例。
express-formなどを使ってあらかじめreq.params.emailの値にバリデーションをかけておく。

auth: function(req, res) {
  User.findOne({ where: { email: req.params.email } }).then(function(user) {
    if (!user) {
      // response
      // 404 NOT FOUND
      // { user: null }
      res.status(404).json({ user });
    }

    user.authenticate(req.params.password, function(err, isValid) {
      if (err) {
        res.status(500).json({ err });
      }
      res.json({ user });
    });
  });
}