Deviseのモヤモヤを解消して快適なRailsライフを送ろう！

[Zenn](https://zenn.dev/)に投稿した記事の転載です。
https://zenn.dev/kitabatake/articles/start-to-like-the-devise

この記事は、Railsのユーザー認証機能のgemとして有名なDeviseを使っていて感じがちな、モヤモヤを解消することを目的としています。
私自身がモヤモヤを感じていたため、このようなタイトルにしましたが、単に「Deviseに対する理解を深める」目的で読んでいただければと思います。

[Devise](https://github.com/heartcombo/devise) はとても便利ですよね。
installして、configファイルを設置して、モデルの設定をして、routes.rbに`devise_for`を書くだけで導入できます。
たったこれだけで、、

- ユーザーの新規登録画面の作成
- ログイン画面の作成
- パスワードを忘れた際の画面の作成
- `authenticate_user!`や`user_signin?`のようなユーザー認証向けの各種メソッドの付与
- etc...

といったユーザー認証機能に関する様々なことをしてくれます。凄いです。

「最低限の設定だけで、多くのことをやってくれること」の代償として、「Deviseがやってくれることの全容が掴みづらい」という点があると思います。
「ユーザー認証」というプロジェクトにおいて大事な部分を担っているのに、その全容が掴めていない状態というのはモヤモヤしてしまいますよね。
もちろん、ちゃんと調べたら解るのですが、ちゃんと調べるのは根気と時間がいるので、モヤモヤを放置してしまっている方も多いのではないでしょうか。

また、Deviseは様々なプロジェクトに導入できるように、かなり柔軟な設定ができるようになっています。
柔軟な設定を可能にするためには、内部では複雑な仕組みが必要です。
そのため、ちょっとした調整作業であっても、内部の処理を把握する必要がある作業は、難しくなってしまいます。
このような、手触り感がなく、ブラックボックス感を感じてしまうことも、モヤモヤを感じてしまう点だと思います。

この2つの **「全容を掴みづらい」** と **「ブラックボックス感がある」** のモヤモヤの原因を、次のようなアプローチで解消することを試みます。

1. 前提知識として「Deviseにとってユーザー認証する」とは何なのかを明確にする
2. Deviseがやってくれる仕事を具体的に知ることで、Deviseの全容を掴む
3. Deviseの内側を把握することで、ブラックボックス感を解消する

それでは、順に見ていきましょう。


# Deviseにとって「ユーザーを認証する」とは

まず前提知識として、Deviseにとって「ユーザーを認証する」とはどういう意味なのかを明確にしておきたいと思います。

一般的なWEBサービスにおいてユーザー認証とは、「アクセスしてきたユーザーが何者かを確認すること」と表現できると思います。
Deviseにとって「何者」に当たるのは「モデルのレコード」です。
したがって、Deviseにとって「ユーザーを認証する」ということは、 **「アクセスしてきたユーザーがどのモデルのどのレコードかを確認する」** と表現できます。

「どのモデルか」は、認証画面のURLによって決まります。
「どのレコードか」は、デフォルトでは`email`と`password`を使って照合します。
（照合する方法は他にもありますが、ここでは細かい説明は不要という意図で、省略します）

また、認証対象のモデルを複数設定できるのが、Deviseの柔軟な部分です。
仮に、UserモデルとCustomerモデルを認証用に使いたい場合は、routes.rbに次のように記述します。

```ruby
devise_for :users
devise_for :customers
```

この場合、`/users/sign_in` がUserモデルの認証画面のURLとなり、`/customers/sign_in`がCustomerモデルの認証画面のURLとなります。

まとめますと、
- Deviseにとって「ユーザーを認証する」とは **「ユーザーがどのモデルのどのレコードかを確認する」** こと
- 認証対象のモデルは複数設定でき、ユーザーをどのモデルとして認証するかはURLによって決まる

Deviseを理解する上で、この2点を把握しておくことが大事だと思ったので冒頭に説明しました。


# Deviseの仕事の全容を把握する

Deviseはユーザー認証機能を実装する上で必要な仕事を **色々** やってくれます。
この **「色々」** の部分を具体的に知る事で、モヤモヤの原因の1つでもある「全容を掴みづらい」を解消する狙いです。


Deviseがやってくれる仕事は大きく分けると次の3つがあります。

1. ユーザー認証の基本的なメソッドの付与
2. 認証用モデルごとのhelper系メソッドの作成
3. モジュールごとに必要な仕事

**1. ユーザー認証の基本的なメソッドの付与** は、Deviseをinstallした時点でApplicationControllerに対して、`sign_in`メソッドなどの、ユーザー認証の機能に必要な基本的なメソッドを付与してくれます。

**2. 認証用モデルごとのhelper系メソッドの作成** は、routes.rbファイルの`devise_for`が呼び出された時点で、`current_user`メソッドのなどの、認証用モデルの名前が入ったメソッドを作成してくれます。

**3. モジュールごとに必要な仕事** は、routes.rbファイルの`devise_for`メソッドと、モデル内の`devise`メソッドのモジュールの設定に応じて、それぞれのモジュールの機能を実現するために必要な仕事をしてくれます。

それぞれ詳しく見ていきましょう。


## ユーザー認証の基本的なメソッドの付与

Deviseのgemをinstallするだけで、ApplicationControllerにユーザー認証用の基本的なメソッドを付与してくれます。
数が多いので全ては紹介できませんが、どういったものが付与されるかがイメージ出来るように、いくつかピックアップしてみます。

### sign_in

ユーザーとモデルのインスタンスを紐づける（ログイン）

```ruby
sign_in(:user, @user)
```

### sign_out

ユーザーとモデルのインスタンスの紐付けを解除する（ログアウト）

```ruby
sign_out(:user)
```

### after_sign_in_path_for

ログイン後にリダイレクトするパスを取得する

```ruby
after_sign_in_path_for(:user)
```

もっと詳しく知りたい方は、下記のディレクトリを見てみてください。
https://github.com/heartcombo/devise/tree/dfbed22cee617992e5f846fdef58b724b6b32ff9/lib/devise/controllers
このディレクトリにある全てのModuleが`ApplicationController`にincludeされます。
`ApplicationController`にincludeされるということで、全てのControllerから呼び出すことができるようになります。


## 認証用モデルごとのhelper系メソッドの作成

`curret_user`や`authenticate_user!`のような、Deviseを使ったことがある方にはおなじみのメソッドを作ってくれます。
これらのメソッドは認証用のモデルごとに作られますので、例えば`User`モデルと`Customer`モデルを認証用に使っている場合は、`current_user`と`current_customer`が、それぞれ作られます。

作られる場所は、`ApplicationController`にincludeされる、`Devise::Controllers::Helpers`というModuleに作成されます。
したがって、全てのControllerから呼び出すことができるようになります。

これらのメソッドはroutes.rbに記述する`devise_for`の中で作られます。
文字通り動的にメソッドを作るので、多くのIDEでは「Jump to Definition」でこれらのメソッドにJumpできない状態になってしまっています。
メソッドを作っているコードは [こちら](https://github.com/heartcombo/devise/blob/dfbed22cee617992e5f846fdef58b724b6b32ff9/lib/devise/controllers/helpers.rb#L112) から見れますので、気になる方はぜひみて見てください。

作ってくれるメソッドは次の4つです。（認証用のモデルとして`User`が設定されている想定）

|メソッド名|概要|
|---|---|
|**authenticate_user!**|認証されているかどうかを確認し、認証されていない場合は認証失敗時のアクションを行う（例:トップページにリダイレクトする）|
|**user_signed_in?**|認証されているかどうか|
|**current_user**|認証されているモデルのインスタンスを取得する|
|**user_session**|認証用モデルのセッション情報にアクセスする|

また、`authenticate_user!`以外のメソッドはViewからも呼び出せるようにしてくれています。
（内部でControllerの [helper_method](https://api.rubyonrails.org/classes/AbstractController/Helpers/ClassMethods.html#method-i-helper_method) を呼び出している）



## モジュールごとに必要な仕事

モジュールとは、ユーザー認証に関連する「機能」のまとまりのことです。
認証用モデルごとに「どのモジュールを使うか」を設定できます。

設定はモデルの`devise`メソッドで行えます。
デフォルトでは次のように、5つのモジュールを使う形で設定されます。

```ruby
class User < ApplicationRecord
  devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable
end
```

モジュールごとに必要な仕事は、「モジュールの機能」ごとに変わってきますが、大まかに分けますと、「Routesを定義する」部分と「モデルを拡張する」部分に分けられます。
具体的な仕事内容を把握するために、2つのモジュールについて説明します。

### **Database Authenticatable** モジュールの仕事

**Database Authenticatable**は、「DBに保存されたパスワードを使ってユーザーを認証する機能」のモジュールです。
この機能を実現するために必要な下記のRoutesが定義されます。

- ログイン画面 **[GET] /users/sign_in** to devise/session#new 
- ログイン処理 **[POST] /users/sign_in** to devise/session#create  
- ログアウト処理 **[DELETE] /users/sign_out** to devise/session#destroy

Routesに対応するアクションの実装は（ControllerとView）gem内の [appディレクトリ](https://github.com/heartcombo/devise/tree/dfbed22cee617992e5f846fdef58b724b6b32ff9/app) に用意されています。
gem内のappディレクトリ以下のアクションの実装を、Routesに設定できる仕組みについて知りたい方は [Rails エンジン入門](https://railsguides.jp/engines.html) を見ていただければと思います。

モデルは次のように拡張されます。

- ユーザーを照合するための`encrypted_password`と`email`を必須のカラムとする
- パスワード認証に必要な各種メソッドを追加する
    - **password=(new_password)**: パスワードをセットする
    - **valid_password?(password)**: パスワードが正しいかどうかをチェックする

他にもたくさんメソッドが追加されますので、詳しく知りたい方は [rubydocのドキュメント](https://www.rubydoc.info/github/heartcombo/devise/master/Devise/Models/DatabaseAuthenticatable) をご覧ください。


### **Validatable** モジュールの仕事

**Validatable**は、emailとpasswordの「入力の有無」や「形式のチェック」などのValidationをしてくれるモジュールです。
Routesはなく、モデルの拡張も最小限です。
主な仕事は、モデルのValidation用の`validates_presence_of`メソッドや`validates_format_of`を使って、emailとpasswordに対してValidationの設定を行うことです。

---

このように「モジュールの機能」によって必要な仕事は変わりますが、他のモジュールも大体このようなことをやっています。
他のモジュールについては、[DeviseのREADME](https://github.com/heartcombo/devise) の冒頭にモジュールごとにrubydocへのリンクが貼られており、そちらに詳細が書かれています。


以上、Deviseがやってくれる仕事を具体的に把握することで、モヤモヤの原因の１つである「全容を掴みづらい」を解消することを試みてみました。
10個もモジュールがあるので完璧に把握することは難しいですが、次のように認識しておけば、だいぶスッキリするのではないでしょうか。

- 基盤部分とモジュールごとの部分を分けて捉える
- 基盤部分の仕事は、主にApplicationControllerにユーザー認証に必要な基本的なメソッドを付与すること
- モジュールごとの仕事は、主に「Routesの定義」と「モデルの拡張」
- モデルの拡張について詳細を知りたい場合はrubydocを見る

Deviseの仕事の全容が掴めたところで、次は認証処理の内側を把握する事で「ブラックボックス感の解消」を試みたいと思います。


# 認証周りの処理の内側を把握する 

Deviseの認証周りの処理の内側を見ていきます。
認証周りの処理とは、具体的に言いますと、「ログイン処理」、「ログアウト処理」、「ログインしているユーザー情報の取得処理」です。

「ログイン処理」は少しややこしいので後回しにして、まずは「ログアウト処理」と、「ログインしているユーザー情報の取得処理」を見てみましょう。

## ログアウト処理

ログアウト処理は`Devise::Controller::SignInOut`に定義されている`sign_out`メソッドで行います。
`Devise::Controller::SignInOut`は`ApplicationController`にincludeされるので、全てのControllerから呼び出すことができます。

sign_outメソッドの使用イメージ:

```ruby
sign_out(:user)
```

`sign_out`メソッドのコードを貼り付けてみます。
しっかりと読む必要はないですが、ざっと見でもいいので、雰囲気を感じてもらった方良いかなと思い。

[lib/devise/controller/sign_in_out.rb](https://github.com/heartcombo/devise/blob/dfbed22cee617992e5f846fdef58b724b6b32ff9/lib/devise/controllers/sign_in_out.rb#L80) から一部抜粋:

```ruby
# Sign out a given user or scope. This helper is useful for signing out a user
# after deleting accounts. Returns true if there was a logout and false if there
# is no user logged in on the referred scope
#
# Examples:
#
#   sign_out :user     # sign_out(scope)
#   sign_out @user     # sign_out(resource)
#
def sign_out(resource_or_scope = nil)
    return sign_out_all_scopes unless resource_or_scope
    scope = Devise::Mapping.find_scope!(resource_or_scope)
    user = warden.user(scope: scope, run_callbacks: false) # If there is no user
    
    warden.logout(scope)
    warden.clear_strategies_cache!(scope: scope)
    instance_variable_set(:"@current_#{scope}", nil)
    
    !!user
end
```

7行のメソッドですが、実際のログアウト処理は、真ん中の `warden.logout(scope)` の部分です。
`scope`とは、認証用のモデルをシンボルで表したもので、たとえば`User`モデルの場合は`:user`が入ります。
Deviseでは複数のモデルを認証用に使えるので、各処理において、どのモデルへの処理なのかを識別する必要があります。
ということで、ログアウト処理は`warden`というものに委譲していることが解りました。

Wardenとは、ユーザー認証機能を使えるようにしてくれるRack Middlewareです。
https://github.com/wardencommunity/warden

DeviseのREADMEの先頭の文にも「DeviseはWardenをベースにしている」と書かれています。
[DeviseのREADME](https://github.com/heartcombo/devise)  から一部抜粋:
>Devise is a flexible authentication solution for Rails based on Warden

まず、Rack Middlewareについて、一言でざっくり言いますと、「Railsがrequestを処理する前段階で、ひと仕事するもの」という感じです。
Wardenの場合の「ひと仕事」にあたるのが、「ユーザー認証機能を使えるようにする」です。

Rack Middlewareについてちゃんと理解したい方は、この記事が解りやすいと思います。
https://qiita.com/nishio-dens/items/e293f15856d849d3862b

ということで、ログアウト処理の内側を知るためには、Wardenの`logout`メソッドを見てみる必要がありそうです。

### Wardenのログアウト処理

Wardenのログアウト処理は、`Warden::Proxy`クラスの`logout`メソッドに実装されています。
ログアウト処理なので、主な処理内容は「認証したユーザー情報の削除」です。

[lib/warden/proxy.rb](https://github.com/wardencommunity/warden/blob/3f197cee0511c9b3c940204bab706407170d9611/lib/warden/proxy.rb#L266) から一部抜粋:

```ruby
def logout(*scopes)
  # ...
    scopes.each do |scope|
        user = @users.delete(scope)
        
        # ...
    
        session_serializer.delete(scope, user)
    end
    # ...
end
```

こちらにも`scope`というものがありますね。
先ほど説明したように、認証用モデルをシンボル化したもので、Userモデルの場合は`:user`となる形でした。
`*scopes`と可変長引数になっているので、`logout(:user)` や `logout(:user, :customer)`のような指定が可能になっています。

Deviseが複数のモデルを認証用に使えるのは、Wardenのscopeの概念をそのまま活用している、ということになります。
https://github.com/wardencommunity/warden/wiki/Scopes

logoutメソッドのコードを見てみると、scopeごとに2つのデータを削除しています。

⑴. usersインスタンス変数から削除

```ruby
@users.delete(scope)
```

usersインスタンス変数はscopeごとの「認証されたユーザー情報」を保持する変数です。
Deviseの`current_user`や`user_signin?`のメソッドは、この値を参照するので、削除する必要があります。

⑵. Sessionから削除 

```ruby
session_serializer.delete(scope, user)
```

Sessionというワードが出てきたので、ざっと説明してみたいと思います。
既に知っている方は、以下2つの段落は読み飛ばしていただいて構いません。

Sessionとは、アクセスをまたいでデータを保持する仕組みです。
ユーザー認証機能で考えた場合、一度ログインしたら、それ以降のアクセスも「ログインされた状態」を引き継ぐ必要があるので、この仕組みが必要になります。
データを保持する場所を一般的にSession Storageと呼び、RedisやDatabaseやCookieなどをSession Storageとして活用できます。

Session Storageは、サイトにアクセスされた全てのユーザーのSessionデータを保持しています。
したがって、Session Storageの中から、アクセスしてきたユーザーのSessionデータを判別する必要があり、そのためにSession IDというものを使います。
Session IDはユニーク（一意）な文字列でして、最初のアクセスの際にサーバーが生成します。
生成したSession IDをCookieを通してブラウザに送り、また、ブラウザーからサーバーに送られます。
このSession IDのおかげで、サーバーはアクセスをまたいでユーザーを識別することが可能になります。

ここまで、Sessionについてざっくりと説明してみました。
SessionやCookieについてはWEB上に色々わかりやすい記事があるので、もっと詳しく知りたい方は検索してみると良いと思います。

SessionもRack Middlewareとして実装されていて、Wardenの前段階で、Sessionデータを操作できるようにしてくれます。
具体的には、CookieのSession IDを元に、Session Storageの中から対象ユーザーのSessionデータを特定して、読み書きできるように準備してくれる感じです。

Sessionの読み書きは次のような形で行えます。

```ruby
env["rack.session"]["foo"] = "abc" # key:fooに対して"abc"を書き込む
env["rack.session"]["foo"] # key:fooの値を読み取る
env["rack.session"].delete("foo") # key:fooを削除
```

Rack Middlewareは、envを通してやり取りするので、こういった形になっています。
ちなみに、DeviseからWardenを使うのも `env["warden"].logout(:user)` といった形になります。

少し脱線してしまいました。
Wardenのlogoutメソッドの下記のコードで、セッションから「認証されたユーザー情報」を削除しているという話でした。

```ruby
session_serializer.delete(scope, user)
```

Sessionデータの「認証されたユーザー情報」のKeyは`warden.user.{scope}.key`という形式です。
したがって、上記のコードはscopeとして`:user`が指定された場合、内部で下記のようなコードを実行します。

```ruby
env["rack.session"].delete("warden.user.user.key")
```

登場人物が多くて少し複雑なので、ログアウト処理の全体の流れを図で表してみました。

![ログアウト処理のイメージ図](https://storage.googleapis.com/zenn-user-upload/hfqsr18sd8cxbumwiv4lvbpx6uqc)

以上、ログアウト処理の内側をのぞいてみました。
少々複雑な構成になっていますが、登場人物とSessionでのデータの持ち方を把握できたことで、だいぶブラックボックス感が薄れてきたのではないでしょうか？


## ログインしているユーザー情報の取得処理

続いて、「ログインしているユーザー情報の取得処理」の内側を見ていきます。
ログアウト処理の説明で、WardenやSessionについて触れたので、こちらはサクッといきます。

「ログインしているユーザー情報の取得処理」は、Deviseのメソッドで言いますと`current_user`や`user_sign_in?`です。
これらのメソッドは、認証用のモデルごとに動的に生成されているメソッドでした。
メソッドを生成している部分のコードを見てみます。


[lib/devise/controller/helper.rb](https://github.com/heartcombo/devise/blob/dfbed22cee617992e5f846fdef58b724b6b32ff9/lib/devise/controllers/helpers.rb#L112) から一部抜粋:

```
def #{mapping}_signed_in?
  !!current_#{mapping}
end

def current_#{mapping}
  @current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
end
```
`#{mapping}`の部分は、認証用のモデルごとに動的に変わる部分でして、Userモデルの場合は`user`が入ります。

Userモデルの場合に生成されるメソッドのコード:

```ruby
def user_signed_in?
  !!current_user
end

def current_user
  @current_user ||= warden.authenticate(scope: :user)
end
```

`user_sign_in?`メソッドは`current_user`の結果をBoolに変換しているだけですね。
`current_user`メソッドもたった1行で、Wardenの`authenticate`メソッドを呼び出して、結果をcurrent_userインスタンス変数に格納しています。
インスタンス変数に格納することで、2回目以降の`current_user`へのアクセスでは、直接インスタンス変数を返すことになるので、`warden.authenticate`の呼び出しが省略されます。

ということで、ログアウト処理と同様に、主な処理はWardenが行なっています。
Wardenの`authenticate`メソッドの内容を見ていきます。

[lib/warden/proxy.rb](https://github.com/wardencommunity/warden/blob/master/lib/warden/proxy.rb#L109)

コードが少し入り組んでいるので、こちらには記載しませんが、やっていることは主に次の2つです。

1. Sessionに認証されたユーザー情報があれば、それを返す
2. Strategyを元にユーザー認証処理を実行する

⑵のStrategyについては、次のログイン処理の部分で説明しますので、ここでは触れません。

⑴の「Sessionから認証されたユーザー情報を取得する」ですが、Sessionデータに関しての基本的な部分は「ログアウト処理」のところで説明したので省略します。
ここでは、「ユーザー情報をSessionデータに変換する処理」と「Sessionデータからユーザー情報に復元する処理」について触れてみたいと思います。

Deviseにとって「ユーザー情報」は「モデルのインスタンス」です。
Sessionに保持するデータは「モデルのID」です。

したがって、Sessionにユーザー情報を格納するとき「モデルのインスタンスをIDに変換」して、Sessionからユーザー情報を取得するとき「IDからモデルのインスタンスに変換」する処理が必要になります。
Wardenにそれぞれの処理を設定するオプションが用意されていて、Deviseがこの設定をしています。

- Sessionへユーザー情報を格納する処理の設定: `serialize_into_session`
- Sessionからユーザー情報を読み取る処理の設定: `serialize_from_session`

設定している場所は下記ですが、かなり抽象化されているので解りにくいかもしれません。
[lib/devise.rb](https://github.com/heartcombo/devise/blob/dfbed22cee617992e5f846fdef58b724b6b32ff9/lib/devise.rb#L476) 

細かい処理の内容はまで把握する必要はないと思いますが、WardenとDeviseの関係性は理解しておくと良いと思います。
Wardenは、モデルやRailsの存在を知らずに、抽象的な形の「ユーザー情報」のみを扱っていて、具体的な処理はオプションとして設定できるようにしてくれています。
Deviseは、Wardenが提供している「ユーザー認証機能」をRailsから活用できるようにしてくれています。
Deviseは、**RailsとWardenの橋渡し役をしてくれている**と表現できるかもしません。


## ログイン処理

最後にログイン処理を見てみましょう。
ログイン処理として、ユーザーを認証する方法は2つあります。

- `sign_in`メソッドに直接ユーザー情報を渡す
- Wardenの`authenticate`メソッド経由でStrategyを実行する

それぞれ説明していきます。

### sing_inメソッドに直接ユーザー情報を渡す

このメソッドは前述した「Deviseの全容を掴む」の中の「ユーザー認証の基本的なメソッドの付与」で、ApplicationControllerに付与される認証用のメソッドとして紹介したものです。
したがって、全てのControllerから、次のように呼び出すことができます。

```ruby
sign_in(user)
```

[Omniauth](https://github.com/omniauth/omniauth) でソーシャルログインを実装する際のcallbackのアクションでは、こちらのメソッドを使って認証処理を行う場合が多いと思います。

[Deviseのsign_inメソッド](https://github.com/heartcombo/devise/blob/dfbed22cee617992e5f846fdef58b724b6b32ff9/lib/devise/controllers/sign_in_out.rb#L33)

このメソッドもやはり、主要な処理はWardenに委譲する形で、Wardenの`set_user`メソッドを呼び出しています。

[Wardenのset_userメソッド](https://github.com/wardencommunity/warden/blob/3f197cee0511c9b3c940204bab706407170d9611/lib/warden/proxy.rb#L170)

Wardenの`set_user`メソッドでは次の2つの処理をしています。

- `users`変数にユーザー情報を格納
- Sessionにユーザー情報を格納

当然ながら、ログアウト処理と逆のことをやっていますね。
users変数やSessionデータに関しては既に説明したので、ここでは説明を省略して、2つめのログイン処理の方法の説明に移ります。

### Wardenの`authenticate`メソッド経由でStrategyを実行する

**Database Authenticatable**モジュールのログイン処理のアクション "devise/session#create" でこの方法を使っています。

[app/controllers/devise/session_controller.rb](https://github.com/heartcombo/devise/blob/dfbed22cee617992e5f846fdef58b724b6b32ff9/app/controllers/devise/sessions_controller.rb#L18) から一部抜粋

```ruby
# POST /resource/sign_in
def create
    self.resource = warden.authenticate!(auth_options)
    
    # ...

    respond_with resource, location: after_sign_in_path_for(resource)
end
```

Wardenの`authenticate!`メソッドでは、既にユーザー認証済みの場合はそのユーザー情報を返し、認証されていない場合はStrategyを実行することで、ユーザー認証を試みます。

Strategyとは、requestのデータを元にユーザー認証を行う仕組みです。
https://github.com/wardencommunity/warden/wiki/Strategies

Strategyは次の2つのメソッドを実装する必要があります。
- `valid?`: requestのデータを元にStrategyを実行するかどうかを判断する
- `authenticate!`: requestのデータを元にユーザーを認証する

これらのメソッドを定義したクラスを、予めWardenのstrategiesに設定しておくと、`authenticate!`メソッドが呼ばれたときに、自動的に呼び出してくれるようになります。

Deviseがこの仕組みを、**Database Authenticatable**モジュールと**Rebemerable**モジュールのユーザー認証に使っています。

たとえば、**Database Authenticatable**モジュールの場合は次のような実装をしています。

---

`valid?`メソッド:
- 対象の認証モデルがパスワードによる認証を許可しているか
- 対象のアクションがパスワードによる認証を許可しているか
- requestのデータに認証のパラメーターが設定されているか（デフォルトでは `email` と `password`）

`authenticate!`メソッド:
- `email`と`password`から対象のユーザーを探す

---

Strategyの仕組みを活用することで、ログイン処理のアクションの実装から、ユーザー認証の処理を切り離すことができます。
前述した、"devise/session#create" アクションの実装も、「requestのパラメーターのチェック」や、「モデルからレコードをfindする処理」もなく、ただ `warden.authenticate!` を呼び出しているだけでした。

一方で、全体の構造としては複雑になってしまっているので、一概にStrategyが「良い仕組みである」と評価することは難しいと思いました。
「Strategyという仕組みを使っている理由」を明確にして、綺麗に締めることができればと思いましたが、このように設計に関することを考察することは難しいですね。

「Strategyという仕組みを使っている」ということを知っているだけでも、ブラックスボックス感を柔らげてくれると思うので、まあこのレベルの掘り下げ度で、記事の目的としては良いのかなと思いました。


# 終わりに

Deviseを使っていて感じるモヤモヤの原因を **「全容を掴みづらい」**,**「ブラックボックス感がある」** と定めて、それぞれを解消することを試みてみました。

この記事を読んでいただいたことで、皆さんのモヤモヤを少しでも解消できていましたら嬉しく思います。

ところで、Deviseに関して色々とネガティブな意見を聞くことがあります。
私も漠然とそういった気持ちを持ってしまっていました。

ネガティブな気持ちを掘り下げると、

- 今回のようなモヤモヤによるもの
- 設計や思想に関するもの（例: モデルに色々入れ込むことは良い設計ではない）

があると思います。

モヤモヤによるものは、主に勉強不足からくるものだと思うので、毎度掘り下げるのは現実的に大変ですが、OSSとして使わせていただいている立場として、謙虚でいたいなと思いました。

設計に関するものは、取り組んでいるプロジェクトの問題領域や開発体制などに絡んで、色々な意見があるように思います。
今回見てきたように、Rack Middlewareとして色々なパーツも使えるので、プロジェクトに応じて自分で認証機能を作るのも１つの現実的な方法だと思います。

設計に関して考察する場合は、今年（2020年）話題になった、[パーフェクトRails著者が解説するdeviseの現代的なユーザー認証のモデル構成について](https://joker1007.hatenablog.com/entry/2020/08/17/141621) という記事が参考になると思います。


以上、最後まで読んでいただいてありがとうございました！
モヤモヤを解消することで、少しでも快適なRailsライフを送る手助けができましたら嬉しく思います！