前回にクッキーの付与の記事を書きましたが、今回はもう少し詳しくRailsでのCookieの仕様を見ていきたいと思います。今回は前回に引き続き、RailsをAPIとして扱う場合について見ていきます。なので、クロスオリジンのフロントエンドアプリに対して付与するサードパーティークッキーとしての位置づけで見ていきます。
Cookieを保存する方法
Cookieに値を保存する方法としては以下の2種類あります。これらをコントローラーから呼び出すとRailsがよしなにクッキーを付与します。ミドルウェアの詳しい挙動については今回は割愛しますが、詳しく紹介している記事を貼っておきます。(難しい)
- #session
- #cookies
- #encrypted
- #permanent
- #signed
使い方は下のように書きます。
session[:session_user_id] = 1
cookies[:non_user_id] = 1
cookies.encrypted[:encrypted_user_id] = 1
cookies.permanent[:parmament_user_id] = 1
cookies.signed[:signed_user_id] = 1
この5種類をCookieに保存したときに保存されるCookieとしては以下になります。

全体が黄色くなり警告がでています。この理由は画面上にも出ていますが、SameSite属性がNoneになっているにも関わらず、Secure属性にチェックがついていないためです。
Secure属性をtrueにする設定を加えます。
session[:session_user_id] = 1
cookies[:non_user_id] = {
value: 1,
secure: true
}
cookies.encrypted[:encrypted_user_id] = {
value: 1,
secure: true
}
cookies.permanent[:parmament_user_id] = {
value: 1,
secure: true
}
cookies.signed[:signed_user_id] = {
value: 1,
secure: true
}
すると警告が消えて、次のリクエストからはきちんとCookieが送られるようになります。

#signedと#encrypted
#signedと#encryptedによって作られたCookieの違いですが、署名されたものか暗号化されたものかという違いがあります。Cookieの内容の改ざんを防ぎたい場合は#signedを使用し、暗号化したい場合は#encrytedを使用します。
署名の検証や暗号化されたクッキーの復号にはsecret_key_base
の値が使われます。
#signedメソッドで作られたクッキーの値は署名されているだけなので、値はデコードして読み取ることができます。実際にデコードしてみた結果が以下です。
※ Rubyの文字コードが複雑な関係で一部文字化けしています。
irb(main):014:0> Base64.decode64('eyJfcmFpbHMiOnsibWVzc2FnZSI6Ik1RPT0iLCJleHAiOm51bGwsInB1ciI6ImNvb2tpZS5zaWduZWRfdXNlcl9pZCJ9fQ%3D%3D--82d86fae2d5533fdaa74e1c40fba4a15351d9cb8')
=> "{\"_rails\":{\"message\":\"MQ==\",\"exp\":null,\"pur\":\"cookie.signed_user_id\"}}\r\xC3\xDC?6w\xCE\x9Fi\xED\x9D\xE7\x9D\xF7}\xD6\x9A\xEF\x87\xB5s\x8D\x1Fm\xAE\x1A\xD7\x9D\xF9\xD5\xDF\\o"
#permanent
#permanent以外のメソッドで作ったクッキーのExpires/Max-AgeはSession
になります。この設定ではブラウザのタブが閉じられるとSessionのクッキーは破棄されます。ブラウザの挙動について少しデモしてみたので、箇条書きですが挙げておきます。
- 新しいウィンドウで同じURLのページを開くと、ExpiredがSessionのクッキーは保持される。
- Chromeを「終了」した場合、ExpiredがSessionのクッキーは破棄される。
- 複数タブで開いている場合、セッションをセットしたブラウザをすべて閉じるとExpiredがSessionのクッキーは破棄される。
他にも色々なパターンがありますが、ExpiredがSessionのクッキーを破棄するにはChromeを終了するか、開いているタブをすべて閉じる(別ウインドウも含める)のが確実でした。
#Sessionメソッドについて
Sessionメソッドを使うと、値がまとめて暗号化されクッキーにセットされます。試しに下で3つの値をセットします。
session[:session_user_id] = 1
session[:session_user_id2] = 2
session[:session_user_id3] = 3
次に下図が実際にセットされたセッションです。一つにまとめられているのが分かります。実際の値はsecret_key_base
で暗号化されているのでブラウザから何の値が保存されているのか知ることはできません。

詳しくはこちら少し古いですが、Sessionの仕様について書かれている記事です。
https://api.rubyonrails.org/v5.2.1/classes/ActionDispatch/Session/CookieStore.html
ただし、検証をクロスオリジンで行っているので、黄色の警告が出ています。これは前述の通りSecure属性がfalseになっていることによるものですが、#sessionを使う場合でSecure属性を設定する方法が分からず断念しました。。
application.rb の設定で config.middleware.use ActionDispatch::Session::CookieStore, secure: true とすることで session でも secure 属性を true にすることができました。
(2023/2/11 追記)
Session管理について
Sessionを管理する方法はCookie以外にも色々あると思います。
セッションをCookieで管理するデメリットとしては、たとえCookieが暗号化されていたとしてもクライアント側で昔のセッションに戻すことができることが挙げられます。
ログイン状態などの管理であれば問題ないと思いますが、買い物かごの状態を保存するなど、過去の状態を悪用できてしまう場合はRedisに移すなどきちんと検討する必要がありそうだと感じました。
さいごに
ここまで読んでいただきありがとうございました。
分かりにくいやアドバイス等ありましたらコメントくださると幸いです。では!
Twitterもやってますので、フォローしていただけるとうれしいです。↓