deviseについて
ZENKIGEN「harutaka」開発チームの藤井です。私が開発に携わっている採用DXサービス「harutaka」のログイン認証等で用いられているdeviseがどういったモノなのか理解するために、Rubyの勉強も兼ねてdeviseについて調査をしたので、その知見をシェアしたいと思います。
deviseについて
deviseとはrailsで作ったwebアプリケーションに簡単に認証機能を実装できるgem
のことです。
ログインやサインアップなどのログイン機能を1から構築するのはかなり大変ですが、deviseを利用することで簡単に実装することが出来ます。
具体的にdeviseでは以下のような機能を実装することが出来ます。
- サインアップ機能
- サインイン機能
- アカウント編集機能
- パスワード変更機能
- メール認証機能
- アカウント凍結機能
💡 deviseの読み方は「デヴァイズ」
装置を意味するdeviceと混合しないように
deviseとUserモデル
deviseは、導入時に認証用モデルを作成することできます。
認証用モデル(テーブル)に作成されるカラムは以下の通り。
+------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------------+--------------+------+-----+---------+----------------+
| id | bigint | NO | PRI | NULL | auto_increment |
| email | varchar(255) | NO | UNI | | |
| encrypted_password | varchar(255) | NO | | | |
| reset_password_token | varchar(255) | YES | UNI | NULL | |
| reset_password_sent_at | datetime | YES | | NULL | |
| remember_created_at | datetime | YES | | NULL | |
| created_at | datetime(6) | NO | | NULL | |
| updated_at | datetime(6) | NO | | NULL | |
+------------------------+--------------+------+-----+---------+----------------+
DBを確認すると、usersテーブルに上記のカラムが存在していることが確認できました。
このことから、弊社でもUserモデルを認証用モデルとして取り扱っていることが分かりました。
deviseの仕事
deviseがやってくれる仕事は大きく分けると次の3つがあると知りました。
- ユーザー認証の基本的なメソッドの付与
- 認証用モデルごとのhelper系メソッドの作成
- モジュールごとに必要な仕事
ユーザー認証の基本的なメソッドの付与
ユーザー認証の機能に必要な基本的なメソッドを付与してくれます。
メソッド名 | 機能 | harutakaで使用 |
---|---|---|
sign_in | すでに認証されているユーザーでサインインする(ログイン) | ◯ |
sign_out | 特定のユーザーまたはスコープをサインアウトする(ログアウト) | ◯ |
認証用モデルごとのhelper系メソッドの作成
認証用モデルの名前が入ったヘルパーメソッドを作成してくれます。
(以下、認証用モデルをUserとします)
メソッド名 | 機能 | harutakaで使用 |
---|---|---|
authenticate_user! | ユーザーの認証を行い、ログイン済ユーザーのみにアクセスを許可する | ◯ |
user_signed_in? | ユーザーがログインしているかどうか | ◯ |
current_user | 現在、ログインしているユーザーのモデルインスタンスを取得する | ◯ |
user_session | ユーザーのセッション情報を設定・取得 |
モジュールごとに必要な仕事
それぞれのモジュールの機能を実現するために必要な仕事をしてくれます。
ここで言うモジュールとは、ユーザー認証に関連する「機能」のまとまりを指します。
モジュール名 | 機能 | harutakaで使用 |
---|---|---|
database_authenticatable | DBに保存するパスワードの暗号化を行う | |
registerable | ユーザーの登録(サインアップ)を行う | ◯ |
recoverable | ユーザのパスワードをリセットできる | ◯ |
rememberable | cookieを使用し、ユーザ情報を保持する | ◯ |
trackable | サインイン回数・時刻・IPアドレスを保存する | ◯ |
validatable | メールアドレスとパスワードのバリデーションをする | |
confirmable | サインインをした際に既にアカウントが登録されているかどうかを確認できる | ◯ |
lockable | 指定された回数サインインを失敗するとアカウントをロックする | ◯ |
timeoutable | 一定時間でセッションを削除する | |
omniauthable | OmniAuthのサポートをする |
deviseとwarden
deviseはwardenという認証周りのRackミドルウェアをベースにしていることが分かりました。
Rackミドルウェアについては、以下記事が大変参考になりました。
ヘルパーメソッドの一つであるcurrent_user
の内部を見てみると、以下のようなソースになっています。
def current_#{mapping}
@current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
end
mappingの部分は認証モデルの名前が入ってくるのですが、warden.authenticate(scope: :#{mapping})
とwardenのauthenticateメソッドが呼び出されています。
wardenのauthenticateメソッドで行われている処理は以下となっています。
1.usersプロパティに値が入っていればそれを返す
2.sessionにuserのデータがあるかを探しに行く。データがあればdeserializeして、usersプロパティにセットして、返す。
3.有効なstrategiesを実行し、認証が成功したら、そのuserを返す
- 有効かどうかの判定の多くはreques.paramのデータに認証に必要なkeyがあるかで判断されるので、認証用のアクション以外では基本的に有効とはならない
- 例えばdatabase_authenticatable strategyでは、emailのkeyがあるかなど。
引用:deviseの調査メモ
すなわち、wardenが認証の仕組みを担っており、その仕組みをrailsに実装する役割がdeviseであると分かりました。
排他ロックの危険性について
調べていく中でdeviseを使うことで排他ロックを引き起こす危険性があると知りました。
trackableモジュールでは、ユーザーのサインイン回数や時刻を記録していますが、同時に同一のユーザーでアクセスを行うと排他ロックが発生してしまいます。
以下は、trackableモジュールによって実行されるSQLです。
UPDATE users SET last_sign_in_at = '2022-07-03 04:55:04',
current_sign_in_at = '2022-07-03 04:55:05',
sign_in_count = 323,
updated_at = '2022-07-03 04:55:05'
WHERE users.id = 1
その他のモジュールも特定の条件付きですが、同一ユーザーを流用することで排他ロックが発生してしまいます。
基本的に同一ユーザーを複数人で使う回すことは無いとは思いますが、こういった危険性があることを念頭に置いておいた方が良さそうです。
まとめ
deviseは便利な反面ブラックボックス感がありましたが、どういった仕組みで認証をしているかを学ぶ事ができて、deviseとは何かを知る事ができました。
また、今回の調査でharutakaとdeviseにどういう繋がりがあるのか知ることが出来たので、自分の携わっているサービスの仕組みについての理解も深める事ができました。
参考&引用
Discussion