Open3

Rackについてメモ

zakkyzakky

request.env の正体

Wardenの内部実装を読んでいると、セッションへの書き込みを行っているのは Warden::SessionSerializer#store メソッドなのだが、そのメソッドは以下の処理をしている。

session[key_for(scope)] = specialized ? send(method_name, user) : serialize(user)

つまるところ、指定したkey名で セッションへの書き込みをしているのだが、より詳細を見ていく。
Warden::SessionSerializer#session メソッドは以下のように定義されている

def session
   env["rack.session"] || {}
end

ここでの envrequest.env と同じなので、 request.env['rack.session'] を実行していることになる。
したがって、Warden::SessionSerializer#store で行っているのは、
request.env['rack.session'][key_for(scope)] = specialized ? send(method_name, user) : serialize(user)
という処理であり、request.env['rack.session'] を操作するラッパーでしかないことが分かる。

そこで、request.envについて見てみる

> request.class
=> #<ActionDispatch::Request

> request.env.class
ActionDispatch::Request::Session

ActionDispatch::Request::Session
https://www.rubydoc.info/docs/rails/ActionDispatch/Request/Session#[]=-instance_method

この ActionDispatch::Request::Session クラスに定義されている [] メソッドを見てみると、その内部で読んでいる id メソッドがやっていることが、以下のメソッドの呼び出しであることが分かる

def self.find(req)
  req.get_header ENV_SESSION_OPTIONS_KEY
end

ここで req.get_headerrequest.get_header と同じであり、 ENV_SESSION_OPTIONS_KEY は Rack::RACK_SESSION_OPTIONS である。
つまり、request.get_header Rack::RACK_SESSION_OPTIONS を呼んでいることになる。
というわけで、結局はRackで定義しているHeaderの値を取得したり書き込んだりして、セッションの実現をしているということが分かる。

zakkyzakky

Wardenのauthenticateのメソッドが返すオブジェクト

Warden::Manager.serialize_from_session を定義している箇所を見ればわかる

zakkyzakky

どのドメインにてセッションを作成・維持するか

セッションとはつまり、状態のこと。
ユーザーエージェント(ブラウザ)ごとに、ある状態を維持するための仕組みとして、HTTPリクエスト時にCookieを送る。
Cookieの中には任意の文字列が入っており、HTTPリクエストを受けたサーバ側はその文字列を解釈して、リクエストを送ってきたユーザーエージェントが何者かを判定する(=認証)
Cookieを生成し、どのドメインに向けたリクエストの際はCookieも一緒に送るかを指定する権限を持つのは、サーバー側である。レスポンスヘッダにSetCookie ヘッダを付与することで任意のCookieをセットできる。
リクエスト時にCookieを添付するということは、そのリクエスト先のサーバーに対して「私はこういう素性のユーザーエージェントです」と自己紹介することとみなせる。
その自己紹介が真正であるとサーバー側が検証できることを、認証が通ったこととみなせる。
CookieのオプションであるDomain属性は、どのドメインに対するリクエストの際に自身(=Cookie)を添付するかを指定するものであった。
つまり、送付先のドメインを指定することで、そのCookieによって自分自身(=ユーザーエージェント)が何者であるかを証明しようとする相手たるサーバーを限定することができる。
これは、ユーザーエージェントはCookieを用いて、どのドメインに自身のセッションを管理させるかを指定できるということになる。

Rackを使ったセッション管理

  • RackはWebサーバが受け取ったHTTPリクエストをいい感じに整理し、ラップしたオブジェクトを作ってくれる
  • そのオブジェクトの中には、ユーザーエージェントから送られたCookieの情報も含まれている
  • このCookieの情報を解釈することで、セッションの生成・維持が可能となり、認証/認可を実現できる。
  • Wardenは認証/認可のインターフェースを提供するgemだが、Rackの存在をベースとしている。直接HTTPリクエストを解釈しその情報を格納するという責務はRackに任せ、Rackが用意してくれたHTTPリクエスト/レスポンスを解釈/生成するインターフェースを用いて、認証と認可の操作をWardenは提供している。
  • Railsの場合は、Rackを生で使わずActionDispatchを用いるので、ActionDispatchが提供するインターフェースを用いるよう最適化したgemである rails-warden というgemがあるが、このgem自身ももちろん Wardenを便利に使うためのインターフェースを提供するのが責務である

ログインについて

ActionDispatch::Request::Session にセッション情報を、Railsでは格納している

> request.env['rack.session']
=> #<ActionDispatch::Request::Session:0x00007f9ed5bd7830 ...>

WardenのSessionSerializerをみると、以下のように sessionkey_forstoreメソッドが定義されている

def session
  env["rack.session"] || {}
end

def key_for(scope)
  "warden.user.#{scope}.key"
end

def store(user, scope)
  return unless user
  method_name = "#{scope}_serialize"
  specialized = respond_to?(method_name)
  session[key_for(scope)] = specialized ? send(method_name, user) : serialize(user)
end

ここで定義されている store メソッドが呼ばれると、request.env['rack.session']["warden.user.#{scope}.key"] に、Warden::Manager.serialize_into_session のブロックで定義している値が、シリアライズされた値として入る (hash形式)
store メソッドが呼ばれるのは、Warden::Proxy#set_user の内部。
set_user が呼ばれるのは、いわゆる current_user メソッドを初回に呼んだとき

これが呼ばれると、デフォルトのscopeでは、 request.env['rack.session']['warden.user.user.key'] でシリアライズされた値を取得できるようになる。

ログアウトについて

Rails Wardenでは、RailsWarden::Mixins::ControllerOnlyMethods#logout が定義されている。
そこで行っているのは、 request.env['warden'].logout と同じ。
つまり、Warden::Proxy#logout を呼んでいる。
ここでは結局のところ、 Warden::Mixins::Common#reset_session!が呼ばれ、request.env['rack.session'].clear を実行している。

request.env['rack.session'].clear によって request.env['rack.session'] の中身がnilになるため、 request.env['rack.session']['warden.user.user.key'] にアクセスしてもシリアライズされた値が取得できず、nilが帰ってくるだけとなる。
これは、authenticate! の戻り値がnilになることを意味するので、fail! が呼ばれて、いわゆるなログアウト状態になる

Cookieについて

request.env['action_dispatch.cookies'] を参照
Rackでの定義はこちら
Class: Rack::Session::Cookie — Documentation for rack/rack (master)
https://rubydoc.info/github/rack/rack/master/Rack/Session/Cookie