【Rails/Devise】ログアウト実装でハマるRouting ErrorとHTTPメソッド設定の落とし穴
はじめに
以前、Railsでアプリ開発をしている際に「ログアウトをしようとするとRouting Errorが発生する」という事象に遭遇しました。
無事に解決できたのですが、私の通っているプログラミングスクールでも同じ事象に困っている方が多くいらっしゃいました。
皆さんの状況を聞くと、多くの場合は私と同じ原因でエラーが発生していました。
一方、Web検索でよくヒットする情報は、本来の解決策ではないように感じます。
そこで、本記事ではDeviseでログアウトが実装できない場合の対処法と、HTTPメソッドの不適切な設定による問題点について解説します。
対象読者
- Ruby on Rails初学者の方
- Deviseを使用している方
筆者環境
以下の環境にて、動作させています。
- Docker環境
- Windows11
- Rails 7.2.2.1
- ruby 3.3.6
- Devise 4.9.4
前提知識
Deviseとは
Deviseは、Ruby on Rails向けの認証ライブラリ(Gem)です。
ユーザー登録・ログイン・ログアウトなどの機能を簡単に実装できます。
Deviseでのログアウト実装時によくあるエラー
Deviseでのログアウトを実装するために、viewファイルに以下のようなコードを書きます。
<%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
そして、ログアウトを行おうとすると、次のようなエラーが発生してしまいます。
Routing Error
No route matches [GET] "/users/sign_out"
本エラーの対策として、Web検索でヒットする対応は、
- ログアウトに使うHTTPメソッドの設定を
DELETE→GETに変更する - HTTPメソッド
GETで"/users/sign_out"にリクエストが飛んできた場合に、ログアウトを実行するルーティング設定を行う
というものです。
簡単に言うと、どちらもHTTPメソッドGETでもログアウトできるようにする設定を行っています。
しかし、この方法にはセキュリティ上の問題点があります。
GETでログアウトをすることの問題点
HTTPメソッドでよく使用されるものはGET,POST,PUT,DELETEかと思います。
それぞれ以下のように利用されます。
| メソッド | 利用目的 |
|---|---|
| GET | リソースの取得(状態変更しない) |
| POST | リソースの作成 |
| PUT | リソースの更新 |
| DELETE | リソースの削除 |
以上を踏まえたうえで、ログアウト操作は本来どのようにするべきでしょうか?
ログアウトは、ログインセッションを破棄する操作になります。
そのため、ログアウト操作はHTTPメソッドDELETEで本来実装されます。
では、HTTPメソッドGETでログアウト操作ができる設定を行うと、どのようなことが起こるでしょうか?
以下のような状況を考えてみましょう。
AAAというWebアプリでHTTPメソッドGETでログアウトできる設定を行ったとします。
悪意ある第三者が、BBBというサイトでhttps://AAA/users/sign_outへのリンクを配置します。
するとどうでしょう、AAAのWebアプリのユーザーはBBBのリンクを踏むと、意図せずログアウトしてしまうのです。
これをCSRF(クロスサイトリクエストフォージェリ)攻撃といいます。
このような危険性があるため、HTTPメソッドのGETメソッドでのログアウトは推奨されません。
本エラーの対応方法
では、今回のエラーへの対処はどのようにしたら良いでしょうか?
そのヒントはエラーメッセージに隠れています。
再度エラーメッセージを確認してみます。
エラーメッセージ
Routing Error
No route matches [GET] "/users/sign_out"
このメッセージを見ると「/users/sign_outというURLに対して GETリクエスト を受け取ったけれど、そのようなパスを処理するルート(ルーティング設定)が見つからない」と言っています。
ルーティング設定を確認すると、/users/sign_outにDELETEリクエストを受け取った場合は、devise/sessionsのdestroyアクションを実行するルーティング設定になっていることが分かります。

では、/users/sign_outへのリンクを生成しているコードを見てみましょう。
<%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
こちらのコードでは、method: :deleteでdestroy_user_session_pathへのリンク、つまりは DELETEリクエスト で/users/sign_outへアクセスするリンクを生成 しようとしています。
一方、今回のエラーから分かる通り、実際には GETリクエスト で/users/sign_outへアクセスするリンクを生成 してしまっています。
Rails 6以前とRails 7以降のリンク生成方法の違い
実は、Rails6系までは、link_toヘルパーのmethod: :deleteでDELETEリクエストのリンクを生成することができました。
これはrails-ujsという仕組みを利用する記述方法でした。
Railsガイド > Rails で JavaScript を使用する (v6.1)
一方、Rails7系以降では、rails-ujsが廃止され、HotwireのTurboが標準搭載されました。このため、method: :deleteでは動作せず、Turboの記法であるdata: { turbo_method: "delete" }を使う必要があります。
Railsガイド > Rails で JavaScript を使用する (v7.2)
実際にコードを以下のように修正すると、正しくログアウト処理が行うことが出来ました。
<%= link_to "ログアウト", destroy_user_session_path, data: { turbo_method: :delete } %>
その他
生成AIに本エラーの対処法を聞くとrails-ujsを有効化するというものを推奨されました。
Rails 7で標準のTurboを使いたい場合は、rails-ujsの有効化は推奨されません。
最後に
今回は、Deviseのログアウト機能で発生するRouting Errorについて、
- GETでログアウトさせる方法の危険性
- Rails 6とRails 7での挙動の違い
- Turboでの正しいリンクの書き方
を解説しました。
Rails 7以降では、method: :deleteの代わりにdata: { turbo_method: :delete }を使うことを覚えておきましょう。
Discussion