Rails8からrails g authentication
で簡単な認証用のコードが生成できるようになりました。これまで認証といえばdevise gemが定番でしたが、rails g authentication
(以下認証ジェネレータと呼びます)もこれから一定使われるようになるのではないかと想像しています。
そんな認証ジェネレータを利用して、生成されたコードを読んでみると気になるポイントがあります。認証用の情報がsession[:user_id]
ではなくcookies.signed[:session_id]
のような形で保持する形式になっているところです。
rails/railties/lib/rails/generators/rails/authentication/templates/app/controllers/concerns/authentication.rb.tt at main · rails/rails
ほとんどのRailsアプリケーションではsession[:user_id]
のような形式で保存しておいた情報をもってログイン済みのユーザを判別しているはずです。なぜsession
を使わないのでしょうか?
認証ジェネレータで生成されるコードでsessionを使っていない理由
理由としては、Action Cableで認証情報を利用したいからだというのが考えられます。Action Cableでは基本的にsession
が使えません。cookiesは読み取り専用ですが使うことができます。
「基本的に使えません」と書いたのは、比較的簡単に使えるときもあるからです。Action Cable の概要 - Railsガイド にあるように、Action Cableではアプリケーション内にマウントして実行する形式と、スタンドアロンと呼ばれる形式でAction Cableだけを動かすプロセスとして実行する形式の二種類があります。前者の形式かつnot APIモード場合、RailsのデフォルトのmiddlewareにActionDispatch::Session::CookieStore
など*1 が入っています。これによりwebsocket接続時のsessionにrequest.session[:user_id]
のようにしてアクセスすることが可能です。
しかし後者の形式もしくはAPIモードのときはsession用のmiddlewareを通らないため、request.session[:user_id]
としてもnilが返ってくるようになります。
すでにsessionを使って認証機能を実装しているRailsアプリケーションにAction Cableを追加したい場合はどうしたらよいでしょうか?Railsガイドにはクッキーセッションを使っている場合の例が書かれています。
以下 Action Cable の概要 - Railsガイド から引用
認証にセッションを含む場合、セッションにcookieストアを使い、セッションcookieの
_session
とユーザーIDのキーとなるuser_id
を使う方法が利用可能になります。
verified_user = User.find_by(id: cookies.encrypted["_session"]["user_id"])
クッキーセッション以外を使っている場合も、Rack Middlewareを追加してやれば恐らくsessionを読み込むこと自体はできると思います(未確認)
なぜAction Cableでsessionが使えないか
なぜAction Cableでsessionが使えないかは僕が探した限りではどこにもドキュメントはなかったのですが、websocketだとsessionは扱いづらいというのが理由ではないかと推測しています。
例えばクッキーセッションだとHTTPレスポンスヘッダのSet-Cookieで値を更新しなければなりませんが、websocket上ではそれはできません。Redisなどにセッション情報を格納していれば可能ですが、Railsとして統一したインタフェースを作ることは難しいと思います。
まとめ
他にもSession
というモデルを使ってDBで認証情報を管理しているところなど、認証ジェネレータには面白いポイントがいくつかあるのですがとりあえずあまりドキュメント化されていないであろうsessionの話をまとめてみました。他にも気になるところがあったらなにか書くかもしれません。
*1:config.session_storeの設定によって変わります