ログアウトも丁寧に実装しよう!脆弱なログアウト機能のリスクとRP-Initiated Logoutについて

こんにちは!ayato(@ay4toh5i)です。

今日は初めてミュージカルを観てきました。同じ演者がひとりで何役も演じたり、演者と楽器隊の息のあった演奏が素晴らしかったです。どうやってタイミング合わせてるんですかね。

さて、本投稿は「Digital Identity技術勉強会 #iddance Advent Calendar 2025」19日目の記事となります。 qiita.com

最近、アプリケーションのセッション管理について考えており、その中でログアウトについて考えていたことを共有していこうと思います。

脆弱なログアウト機能が攻撃に利用されるリスク

ログアウト処理はログイン中のユーザーのセッションを破棄するのが基本的な機能です。 仮に悪意のある第三者がCSRF攻撃などで他人をログアウトできたとしても、単なる嫌がらせにしか見えないかもしれません。

しかしながら、巧妙化する攻撃を想定した場合、他人を故意にログアウトさせることでフィッシング攻撃やセッションCookieの窃取の起点として利用されるなんてことも考えられるのではないでしょうか。

ユーザー認証のセキュリティでは、パスキーの導入などログインの段階に関連するものが注目されがちですが、こういったことを考えるとログアウトも適切に実装しておくことは重要だとわかります。

Webアプリケーションでのログアウトのベストプラクティス

それでは、一般的なWebアプリケーションでのログアウトのベストプラクティスについて復習していきます。

・ログアウト処理は副作用があるのでPOSTメソッドでリクエストする
・ログアウト処理ではセッションを破棄する
・必要な場合、CSRF対策の対象とする
徳丸 浩. 体系的に学ぶ 安全なWebアプリケーションの作り方 第2版[リフロー版] 脆弱性が生まれる原理と対策の実践 (Japanese Edition) (p. 762). (Function). Kindle Edition.

セッションを破棄するのは当然として、CSRF対策が疎かになっているケースもあるのではないでしょうか。 ログアウトでも手を抜かずにCSRF対策をしましょう。Webアプリケーションフレームワークを利用している場合、大抵CSRF Tokenなどの機能が標準で備わっているはずです。

OpenID Connect RP-Initiated Logout 1.0について

単一のドメイン(サブドメインを利用してセッションCookieを共有するような場合を含む)で構成されるようなサービスの場合、上述の対策で十分かもしれません。 しかし、複数のドメインで構成されるサービスにおいては、独立したIdPを用意しOpenID Connect(OIDC)でID連携させることでユーザーの認証を成立させるといったことが行われます。 その場合、メインのサービス(RP)だけではなくIdP側もログアウトさせる必要が生じます。

OIDCにおいてRPを起点としたシングルログアウトを実現する仕様がOpenID Connect RP-Initiated Logout 1.0です。

OIDCを実装したIdPを利用していて、RPを起点としたログアウトを実装するのであれば、この仕様を利用することになるでしょう。 上述の通りログアウトを悪用されないようにRP-Initiated Logoutも適切に実装する必要があります。

仕様の概要

sequenceDiagram
  autonumber
  participant UA as User Agent(ブラウザ)
  participant RP as RP
  participant OP as OP(IdP)
  participant RP2 as 他のRP

  UA->>RP: 「ログアウト」クリック
  RP->>UA: OPのLogout Endpointへ遷移/GET or POST<br/>(id_token_hint, post_logout_redirect_uri, stateなど)
  UA->>OP: RP-Initiated Logout Request

  OP->>OP: id_token_hint検証(iss/aud/sid等)
  alt id_token_hintなし/検証失敗
    OP->>UA: ログアウト確認画面を表示
    UA->>OP: 「はい」(確認)
  end

  OP->>OP: OPセッションを終了
  par ログアウト通知
    OP->>RP: Front/Back-Channelで通知
    OP->>RP2: Front/Back-Channelで通知
  end

  OP-->>UA: post_logout_redirect_uriへリダイレクト(stateを付与)
  UA->>RP: コールバック受信(state検証)
  RP->>UA: ログアウト完了画面

ざっくり概要を説明すると、RPからOPのログアウト用エンドポイントへ所定のパラメータを付与してリダイレクト(もしくはPOST)することで、OPからログアウトするとともにその他のRPへログアウトの通知を行い、必要に応じて事前にOPに登録した任意のURIへリダイレクトするといった仕様です。

詳細な仕様については、OpenID Connect RP-Initiated Logout 1.0を参照いただくとして、RP-Initiated Logoutを安全に利用するにはどうすれば良いのでしょうか。

id_token_hint を利用する

id_token_hint は、ログアウトすべきエンドユーザーのセッションを特定するため、事前に入手したID TokenをOPに連携するためのパラメータです。

このパラメータは、以下のような理由で重要です。

1. 正当なRPからのログアウト要求であることの確認
RPからOPへのログアウト要求は、OP側のセッションの確認やログアウト要求に対する確認画面の表示を挟むためフロントチャネルで実行されます。
そのため、RPからOPへ対象となるユーザーの認証フローで取得したID Tokenを提供し、OPで署名やiss・aud・sid、OP側のセッションとの紐付きを検証することで、正当な要求であることを確認します。

2. 破棄すべきセッションの特定
場合によっては、エンドユーザーが同時に複数のセッションを持っていることもあります。 ログアウト要求があった場合に、どのセッションを破棄すべきなのか、ユーザーやセッションを特定するためにも利用されます。

3. post_logout_redirect_uri の検証
RP-Initiated Logoutでは、post_logout_redirect_uriというパラメータを利用することで、OP側のログアウト処理完了後に特定のURIにリダイレクトさせることができます。 このURIは、OP側に事前に登録した値と一致している必要がありますが、どのRPの設定なのかを特定するためにもID Tokenのクレーム(audなど)が利用されます。

必要に応じてログアウト確認画面を挟む

RP-Initiated Logoutでは id_token_hint がない、もしくはID Tokenが不正、現在とのセッションとの紐付きが認められない場合は、確認画面を表示することになっています。
Authorization Code Flowが中途半端な状態で失敗している場合、OPにはセッションが発行されているが、RP側でID Tokenを取得できていないということもありえそうです。
また、ID Tokenを利用しないポリシーで運用されると言ったことも考えられます。
そのような場合は、ログアウト要求から即座にセッションを破棄するのではなく、確認画面挟んでエンドユーザーの同意を得てから処理しましょう。

post_logout_redirect_uri を利用する場合は、stateも指定する

stateパラメータは、RP側でログアウト要求とコールバックを紐づけるために利用されます。
ログアウト後の遷移先の性質にもよると思いますが、認可リクエスト時と同様、CSRF対策として設定しておくと良いでしょう。

ID Tokenには出来るだけセンシティブな情報を含まないようにする。

ID Tokenを取得するフローやID Tokenの利用方法によっては、メールアドレスや電話番号などの個人情報が含めている場合があるでしょう。 そのため、フロントチャネルでID Tokenをやり取りしたくないと思われるかもしれません。 しかし、OpenID Connect Core 1.0の 5.4. Requesting Claims using Scope Values を振り返ってみると、アクセストークンが発行されるフローでは email 等のクレームはUserInfo Endpointから返却することになっています。

The Claims requested by the profile, email, address, and phone scope values are returned from the UserInfo Endpoint, as described in Section 5.3.2, when a response_type value is used that results in an Access Token being issued. However, when no Access Token is issued (which is the case for the response_type value id_token), the resulting Claims are returned in the ID Token.

5.4. Requesting Claims using Scope Values - OpenID Connect Core 1.0 incorporating errata set 2

id_token_hint は、OIDCの認可リクエストのパラメータとしても定義されていたりと、フロントチャネルでのやり取りで利用されることもあるため、できる限り個人情報を含めないのがベストプラクティスといえそうです。

OPへのログアウト要求はPOSTを好んで使う

ID Tokenに個人情報を含めないのが望ましいですが、すでにID Tokenに個人情報を含めた運用がされているケースもあるでしょう。 個人情報が含まれないケースも併せて、可能であればPOSTでログアウト要求するのが良さそうです。 ID Tokenをブラウザの履歴やアプリケーションやプロキシのログとして露出させず、漏洩の予防になります。

ただし、ここでCookieのSameSite属性が問題になってきます。 仮にOPのセッションCookieのSameSite属性を Lax で運用している場合、クロスサイトなRPからOPへのPOSTリクエストではCookieが付与されません。 そのため、id_token_hint で連携されるRPがログアウトを期待しているユーザー・セッションが「現在ログイン中のユーザーのセッション」と整合しているかを1往復で確認することができなくなってしまいます。

そのため、OPがPOSTでのログアウト要求をサポートする場合、セッションCookieのSameSite属性を None と設定して運用する必要がありそうです。 このようにOIDCの仕様を実現するにあたって、一部の仕様ではクロスサイトのリクエストが必要になるため、例えばAuth0ではセッションCookieのSameSite属性を None で運用しているようです。 1

クロスサイトでのリクエストになるためCSRF Tokenなどは利用できませんが、Fetch Metadata Request Headerを利用することで不正なリクエストを緩和することができそうです。

期限切れのID Tokenを利用することの是非

RP-Initiated Logoutでは、id_token_hintはあくまでもRECOMMENDEDなパラメータであり、client_idやlogout_hintを活用することもできそうです。 しかし、RP-Initiated Logoutの仕様やBitbucketでの議論を踏まえると、基本的にはid_token_hintを利用するという前提がありそうです。

ただ、実際にid_token_hintを利用することを考えると、先日の川崎さんの記事(id_token_hintをサポートしない理由)にもあるようにいくつか問題がありそうです。

1. ID Tokenの有効期限(exp)が切れている可能性がある
用途を考えると、ID Tokenとしては有効期限が切れていると考えられます。 しかし、仕様上はIssuerやセッションとの紐付きが問題なければ、有効期限を過ぎていたとしても受け入れるべきとされています。

When an id_token_hint parameter is present, the OP MUST validate that it was the issuer of the ID Token. The OP SHOULD accept ID Tokens when the RP identified by the ID Token's aud claim and/or sid claim has a current session or had a recent session at the OP, even when the exp time has passed.

2. RP-Initiated Logout - OpenID Connect RP-Initiated Logout 1.0

id_token_hint は、ID Tokenを認証結果の連携のために利用しているというよりは、過去にちゃんと認証できたRP・ユーザーであることを示すヒントとして利用されていると思います。 そのため、expを無視しても良いよねということと理解していますが、流石に署名は検証する前提でよいのではないかと思っています。
ただ、やはり素直なID Tokenの検証ではないと思うので気持ち悪さはありますね。

2. ID Tokenの署名の検証に必要な鍵が利用できない可能性がある
あまりに古いID Tokenが来た場合は、既に鍵を破棄している場合もあるでしょう。 また、場合によっては鍵を明示的に無効化していることもあるでしょう。その場合、ID Tokenの署名は検証できません。 RP-Initiated Logoutでの利用を考えると、鍵をローテーションしたとしても内部ではしばらく保持しておく必要がありそうです。

ただ、仮にID Tokenの署名を検証できなかったとしても、確認画面を表示してカバーすれば良さそうです。
鍵をどのようなポリシーで管理するかは、RP-Initiated LogoutでのID Token利用も含めて検討しましょう。

3. そもそも、client_idやlogout_hint で必要な情報を連携すれば良いのでは?
ID Tokenの有効期限や署名の問題を考えると、client_idとlogout_hint で必要な情報(subやsidなど)を連携すれば良いのでは?という考えも出てきそうです。 しかし、仕様上 logout_hint をどのように利用するかは、OPの裁量に委ねられています。 そのため、logout_hint を利用する場合は、あらかじめRPとOPでどのように運用するか取り決めておく必要性があります。

RPとOPがそれぞれ限定されているのであれば、logout_hintを利用するでも問題ないかもしれません。 しかし、多数のRPとOPが相互に連携することを想定すると、id_token_hintはRPの正当性を表現するだけでなく、相互運用性を担保する意味でも存在意義がありそうに感じます。

最後に

正直、RP-Initiated Logoutの仕様は短いものの、なぜその仕様が必要なのか言語化されていないや実装者に任されている部分が多く、解釈が難しい仕様だと思いました。 そもそも、私のOpenID Connect Core 1.0仕様に対する理解が浅いせいで、RP-Initiated Logoutや id_token_hint に対する解釈が不適切である可能性が多分にあります。

書いてはみたものの、ID Tokenの利用方法は未だにもやもやしているポイントでもあります。 おそらく、正当なRPであることを示すために、ID Token以外の方法があれば良いんだと思います。 Back-Channel LogoutのLogout TokenみたいなのをRP側で作れたら良いのでは? Confidential Clientであれば、client_secret_jwtとかprivate_key_jwtとかを横展開できそう!でもPublic Clientだと?とか色々妄想してます。

仕様策定の全ての議論を追いかけられているわけではないので、また発見があれば更新するかもしれません!

「Digital Identity技術勉強会 #iddance Advent Calendar 2025」は、まだまだ続きます! qiita.com

References