- 確認画面なしでログアウトさせたい!
このような疑問にお答えします。
先日ナビゲーションバーを作っているときに、ログアウトボタンをクリックしたら即時ログアウトできるようにしたいと考えました。
ところが、通常の LogoutView では一度「ログアウトするかどうかの確認画面」が表示されてます。
今回はこれを不要にする方法です。
クリックで即時ログアウトさせるボタンを作成
以下の流れで実装を進めます。
- urls.py でルーティングを行う
- テンプレートにリンクを設置
順番に解説していきます。
urls.py でルーティングを行う
ログアウト用のルーティングを行います。
from django.contrib.auth.views import LogoutView
urlpatterns = [
path("", views.IndexView.as_view(), name="index"),
path("logout/", LogoutView.as_view(next_page=reverse_lazy("index")), name="logout"),
]
自動的にリダイレクトをさせるため、next_page=reverse_lazy()
をas_view()
に渡しました。
ちなみにreverse_lazy()の引数に渡したindex
は4行目のビューを指します。(name="index"
のところを指定している)
テンプレートにリンクを設置
テンプレート HTML には、以下のように書きます。
<form action="{% url 'logout' %}" method="post">
{% csrf_token %}
<input type="submit" value="ログアウト">
</form>
Django 5.0以降のバージョンではLogoutView
に対してPOST
メソッドだけしか受け付けなくなってしまいました。
そのため、上記のようにform
タグで実行する必要があります。
例えば以下のようにa
タグでリンクを作るとGET
メソッドでリクエストが送られてしまうので、エラーが出てしまいます。
<a href="{% url 'logout' %}">ログアウト</a>
LogoutView で GET メソッドが廃止された理由
Django 5.0 からはログアウトビューの GET メソッドが完全に廃止され、POST メソッドを使う必要があります。
この点について、少し詳しく解説します。
廃止された理由
Django のチケット 7989 によると、廃止の理由は「HTTP 1.1 の仕様に基づくもの」です。
つまり「GETおよびHEADメソッドは、取得以外の行動を引き起こすべきではない」という思想からくるものだそうです。
GET や HEAD は安全なメソッドであるべきで、ログアウト操作はアプリの状態を変更するものだから、原則として GET は不適切ということになります。
その他の考えられる理由
フォーラムの議論にはありませんでしたが、潜在的に以下の理由も考えられると思います。
- CSRF 対策
- アクションの非可逆性
- プリフェッチングによる誤操作
それぞれ、解説します。
CSRF 対策
CSRF の一例として、「強制ログアウト攻撃」があります。
GET メソッドでログアウトボタンを実装していると、攻撃者がログイン中のユーザーになりすまして代わりにログアウトさせてしまうものです。
POST メソッドだと CSRF トークンが発行されるので、攻撃者はログイン中のユーザーになり変わることが難しくなります。
またこの「強制ログアウト攻撃」を起点として、さらなる攻撃が加えられることもあるので、やはり CSRF は注意すべきことの一つとなります。
アクションの非可逆性
ユーザーがブラウザの履歴から「過去にログアウトした際に利用したリクエスト」を実行してしまうと、新しいセッションでもログアウトできてしまいます。
これが POST メソッドで実行される場合には、CSRF トークンが付与されているので有効期限が切れたリクエストということで弾かれます。
つまり POST メソッドを使えば、ユーザーが履歴から誤ってリクエストを送信した際にログアウトしてしまうことを防ぐことができます。
プリフェッチングによる誤操作
最近のブラウザは、あらかじめページの内容を読み込みます。(プリフェッチング)
GET リクエストによるログアウトがリンクとして設置されていると、プリフェッチングの段階でログアウトされてしまう可能性があります。
Django のリリースノートと議論
Django 4.1 では、以下のように廃止予告がされていました。
【原文】
Logging out via
GET
requests to thebuilt-in logout view
is deprecated. UsePOST
requests instead.If you want to retain the user experience of an HTML link, you can use a form that is styled to appear as a link.
【Deepl による翻訳】
組み込みのログアウトビューへのGETリクエストによるログアウトは非推奨です。代わりにPOSTリクエストを使用してください。
HTML リンクのユーザーエクスペリエンスを維持したい場合は、リンクとして表示されるようにスタイルされたフォームを使用できます。
出典:Log out via GET
まとめ
Django 5.0 以前は GET メソッド(a
タグを使ったリンク設置)でも実装可能だったので、先日はこれでハマってしまいました。
POST メソッドで実装することで実現できますので、本記事を参考にしてみてください。
コメント