【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.middleware.use ActionDispatch::Cookies # ⭐️この行を追加し、Cookieの書き込みを有効化
# 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
がちゃんと返ってきている)
- APIのレスポンスも問題なし(
-
ActionController::Cookies
はincludeしていたので、読み取り自体はできる
class V1::ApplicationController < ActionController::API
include ActionController::Cookies
-
cookies[Settings.token]
を参照した時に値は消えている
# 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を明示的に削除
# 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.middleware.use ActionDispatch::Cookies
「これで削除されるはず!」と期待したが、残念ながらまだCookieは消えず・・
なんでやねん!(関西弁◎)
・・あ、設定変えてるからコンテナの再起動が必要かもしれない💡
(settings.yml
の内容等を変更した時は普段Sidekiqを再起動している)
早速、Dockerコンテナを再起動してみる
docker compose restart
コンテナ再起動後、無事にブラウザ上のCookieが削除されていることを確認🎉🎉🎉
参考

株式会社ウェイブのエンジニアによるテックブログです。 弊社では、電子コミック、アニメ配信などのエンタメコンテンツを自社開発で運営しております! wwwave.jp/service/
Discussion