LoginSignup
14
16

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-05-26

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

モデル作成

まずモデルを作る。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 });
    });
  });
}
14
16
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
16