実務でdevice_token_authを扱うことになりました。
理解を深めるために、ログイン認証時に実行されるsessions_controllerのcreateアクションをコードリーディングしてみました。
sessions_controllerのcreateアクションとは?
ログイン認証時に実行されるアクションです。
createアクションの処理のはざっくり以下のような流れで構成されています。
- 認証対象のレコードの検索
- 認証処理
- 認証対象のレコードが存在し、認証可能な状態にある場合
- 認証対象のレコードが存在するが、認証可能な状態にない場合
- 1と2いずれにも当てはまらない場合
※createアクション内で呼び出される他controllerや他moduleの処理は深追いして解説はしません。
1. 認証対象のレコードの検索
if field = (resource_params.keys.map(&:to_sym) & resource_class.authentication_keys).first
q_value = get_case_insensitive_field_from_resource_params(field)
@resource = find_resource(field, q_value)
end
field
, q_value
にはそれぞれ以下の値が設定されます。そして、これらの変数を引数にfind_resource
を呼び出して、認証対象のレコードを検索します。
-
field:認証用のカラム(例:email, login_id)
resource_params(認証に関するリクエストパラメータ)のキーとdeviceで設定したauthentication_keys(認証用のカラム名)の重複値が格納される。 -
qvalue:resource_paramsのうち、
field
のキーに対するvalue
この時valueをそのまま格納するわけではなく、deviceの設定でcase_insensitive_keys
(大文字小文字を区別しない)もしくはstrip_whitespace_keys
(空白を無視する) にfield
のキーが指定されている場合は、適切な加工を実施したうえで格納される。
2. 認証処理
認証処理は3つのパターンに分かれます。
1. 認証対象のレコードが存在し、かつそれが認証可能な状態にある場合
認証可能な状態とは、アカウントがロックされておらず、かつメール認証等のアカウント認証も完了している状態を指します。
if @resource && valid_params?(field, q_value) && (!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
valid_password = @resource.valid_password?(resource_params[:password])
if (@resource.respond_to?(:valid_for_authentication?) && !@resource.valid_for_authentication? { valid_password }) || !valid_password
return render_create_error_bad_credentials
end
create_and_assign_token
sign_in(@resource, scope: :user, store: false, bypass: false)
yield @resource if block_given?
render_create_success
冒頭の条件部分にてvalid_params?(field, q_value)
はリクエストパラメータにpasswordキーが存在することをチェックします。
valid_password
にはパスワードの認証結果がbooleanで保持されます。
パスワード認証に失敗した場合は、認証情報が正しくない旨のエラーメッセージを返却します。
認証に成功した場合は、create_and_assign_token
でトークンを生成し、ログイン処理を実行します。
最後にrender_create_success
でレスポンスとして、トークン情報や有効期限等をクライアントに返却します。
補足
valid_for_authenticationや後述のactive_for_authenticationはざっくり「そのアカウントがロックされておらず、かつメール認証等のアカウント認証も完了しているか」を判定するインスタンスメソッドと解釈ください。いずれもlib/devise/models/authenticatable.rbで定義されたメソッドですが、lockableやconfirmableでオーバーライドされており、ブラックボックスな形になっています。
2. 認証対象のレコードが存在するが、認証可能な状態にない場合
対象のレコードは存在するが、ロックされている、メールでのアカウント認証が済んでいない等認証可能な状態になっていない場合の処理です。
elsif @resource && !Devise.paranoid && !(!@resource.respond_to?(:active_for_authentication?) || @resource.active_for_authentication?)
if @resource.respond_to?(:locked_at) && @resource.locked_at
render_create_error_account_locked
else
render_create_error_not_confirmed
end
ロックされている場合はその旨のエラーを、それ以外の場合はアカウントが未認証である旨のエラーを返却します。
※!Devise.paranoid
はDeviseの設定でparanoidモードが無効になっていることを指します。説明を簡略化するために省きましたが、レコードが存在しそれが認証可能な状態にない場合でも、paranoidモードが有効な場合は3の分岐に入ります。
3. 1と2いずれにも当てはまらない場合
else
hash_password_in_paranoid_mode
render_create_error_bad_credentials
end
認証情報が正しくない旨のエラーメッセージを返却します。
hash_password_in_paranoid_mode
はparanoidモードが有効な場合に実行されるセキュリティ対策(タイミング攻撃への対策)の処理だそうです。(paranoidモードについては詳しくないため、別記事にまとめたいと思います。)
今後
createアクションの大まかな流れは以上です。今後createアクション内で呼び出されている処理も気が向いたら読んでいきたいと思います。