🐡

【Rails】APIモードでHttpOnlyなCookieを削除する

に公開

実現したいこと

  • サーバーサイド(Rails)側でCookieを削除できるようにしたい

背景

  • これまではフロントエンド側でCookieの値の操作を行っていた
  • 脆弱性対策でCookieにHttpOnly属性を設定したことによって、フロントエンド側(Nuxt3)でCookieを参照することが出来なくなった
  • 認証情報の受け渡し方法としてAuthorizationヘッダーの利用も取りやめた
  • 従って、HttpOnly属性付きのCookieはJavaScript(フロントエンド側)からの操作ができないため、必ずAPI経由でサーバーサイド(Rails)から削除する必要がある

結論

Frontend

APIリクエスト送信時に、withCredentials: trueオプションを追加し、APIリクエストにCookie情報を含めて送信できるようにする。これにより、クロスオリジン間でもCookieが正しく送受信される。

await this.$api.v1.deleteCookie({ withCredentials: true }); // ⭐️API呼び出し時にCookie情報を有効化

Backend

RailsのAPIモードでは、デフォルトでCookieの書き込みができない。
そのため、ActionDispatch::Cookiesミドルウェアを有効化し、APIリクエストでもCookieを操作(削除含む)できるようにする。

config/application.rb
config.middleware.use ActionDispatch::Cookies # ⭐️この行を追加し、Cookieの書き込みを有効化
app/controllers/v1/xxx_controller.rb
# frozen_string_literal: true

class V1::XXXController < V1::ApplicationController
  before_action :require_login, only: :destroy

  def destroy
    # ⭐️Cookieを削除
    cookies.delete(Settings.token,
                   domain: ".sample.com",
                   path: "/",
                   secure: true,
                   httponly: true,
                   same_site: :lax,
                   expires: Time.zone.at(0))

    render_success
  end

  private

  def require_login
    render_redirection(Api::V1::Response::Code::REQUIRE_LOGIN) unless logged_in?
  end
end

過程 (解決までの道のり)

cookies.deleteしてもブラウザ上のCookieが消えない...!

  • フロントエンド側でAPI呼び出し時にwithCredentials: trueも渡している
    • APIのレスポンスも問題なし(status: 200がちゃんと返ってきている)
  • ActionController::Cookiesはincludeしていたので、読み取り自体はできる
app/controllers/v1/application_controller.rb
class V1::ApplicationController < ActionController::API
  include ActionController::Cookies
  • cookies[Settings.token]を参照した時に値は消えている
app/controllers/v1/xxx_controller.rb
# frozen_string_literal: true

class V1::XXXController < V1::ApplicationController
  before_action :require_login, only: :destroy

  def destroy
    Rails.logger.info("before: #{cookies[Settings.token]}") # before: DcxeHX9kvuyEK3DcHgCeQmFpFkm

    cookies.delete(Settings.token,
                   domain: ".sample.com",
                   path: "/",
                   secure: true,
                   httponly: true,
                   same_site: :lax,
                   expires: Time.zone.at(0))

    Rails.logger.info("after: #{cookies[Settings.token]}") # after: 

ブラウザ上(開発者ツール)で確認した時に該当のCookieが消えないのは何故...?

暫定

  • response.headersが持つSet-Cookieを明示的に削除
app/controllers/v1/xxx_controller.rb
# frozen_string_literal: true

class V1::XXXController < V1::ApplicationController
  before_action :require_login, only: :destroy

  def destroy
    response.headers["Set-Cookie"] = "#{Settings.token}=; domain=.sample.com; path=/; Max-Age=0; Secure; HttpOnly;"

一応これでいける

・・いやいやcookies.deleteで消す方法絶対あるやろ!(なぜ関西弁?)
シランケド。🏇

解決

RailsのAPIモードだと、ミドルウェアを有効にしないとCookieの書き込みができないということが判明。これは、APIモードがデフォルトでセッションやCookieの処理を行わない設計になっているため。

そこで、config/application.rbに以下の行を追加

config/application.rb
config.middleware.use ActionDispatch::Cookies

「これで削除されるはず!」と期待したが、残念ながらまだCookieは消えず・・

なんでやねん!(関西弁◎)



・・あ、設定変えてるからコンテナの再起動が必要かもしれない💡
settings.ymlの内容等を変更した時は普段Sidekiqを再起動している)

早速、Dockerコンテナを再起動してみる

docker compose restart


コンテナ再起動後、無事にブラウザ上のCookieが削除されていることを確認🎉🎉🎉

参考

wwwave's Techblog

Discussion