OAuthやOpenIDでログアウト連携してみたい

ども、新たなチャレンジを始めていろいろと勉強中なritouです。
OpenIDやXAuthについても今までログアウトについてエントリを書いたことがありますが、今一度自分の考えをまとめてみたいと思います。

OAuthでログアウト?何言ってるの?

「OAuthはセッション同期をするものではない。リソースアクセスのための・・・」
って自分も言ってたわけですが、OpenIDの次の動きはOAuthと深くかかわることになりそうだと踏んでいます。
OpenID ConnectとArtifact BindingがIIWでどんな方向で話が進んでいるのかにもよりますが、とりあえずOpenIDとOAuthは仲良しになりそうなのです。

また、SPとConsumerとして別々のサイトをつなぐためにOAuthを使うケースだけではなく、ユーザーから見れば同じサービスなのに裏ではOAuthでまったく別の会社が作ったシステムがつながっていた、なんてことも普通にあると思います。
そんなこんなで、今回はOAuth 2.0でログアウト連携の話をさせてくださいよと。

あとは、「べつにログアウト連携しなくてもいいじゃん。気にするユーザーならちゃんと個別にログアウトするでしょ?」っていう意見があるかもしれません。
いやいや、するわけないじゃないですかと。全部のサイトでログアウトをちゃんとするユーザーばかりならこんなこと考えませんわ。
ということで、今回は「共有PCにおけるログアウト忘れによるセッションのっとりリスク」を抑える的な意味合いで、「セッションをひたすら一度でぶった切ること」を正とします。

ログアウトにおける要件

まず、Server(AuthZ Server)とClientが1:1の場合を考えます。
簡単です。

  • Client側のサイトでログアウトしたらServer側のサイトもログアウトさせたい
  • Server側のサイトでログアウトしたらClient側のサイトもログアウトさせたい

次、Server(AuthZ Server)とClientが1:nの場合を考えます。

  • Client側のサイトのどれか一つでログアウトしたらServer側のサイトもログアウトさせたい
  • Server側のサイトでログアウトしたら全てのClient側のサイトをログアウトさせたい

次、Server(AuthZ Server)とClientがn:1の場合を考えます。
。。。いや、やっぱりこれは考えません。

提案 draft-ritou-oauth-singlesignout?

ノーマルなOAuth 2.0だといろいろ機能追加しないといけないので、OpenID Artifact Binding 1.0 - RCxベースで話を進めます。
About Me | @_Nat Zone

Server/Clientがログアウトエントリポイントを提示

Server/Clientは、それぞれこんな感じで宣言しておけばいいでしょう。

  • Server側
<Service xmlns="xri://$xrd*($v*2.0)">
  <Type>http://openid.net/specs/ab/1.0#singlesignout</Type>
  <URI>http://server.example.com/logout</URI>
</Service>
  • Client側
<Service xmlns="xri://$xrd*($v*2.0)">
  <Type>http://openid.net/specs/ab/1.0#singlesignout</Type>
  <URI>http://client.example.com/logout</URI>
</Service>
<Service xmlns="xri://$xrd*($v*2.0)">
  <Type>http://openid.net/specs/ab/1.0#singlesignout_callback</Type>
  <URI>http://client.example.com/logout_callback</URI>
</Service>

HTTPSは必要かなぁ・・・

Clientがログアウト連携に加わるためのリクエストとServerの処理

OpenID Artifact BindingではOAuthのリクエストに、OpenIDのパラメータをrequest_uriというパラメータからJSON形式でやりとりされます。
Clientはそのリクエストに、以下のようなパラメータを含める必要があります。

例:

"ns.ssout":"http://openid.net/specs/ab/1.0#singlesignout",
"ssout.expires_in":"1209600"
  • ns : 必須。この値があるとき、Server側
  • expire : オプション。Server側がClientへのログアウト連携に有効期限を定めるときはこの値を利用する。

例でいうと、Clientはログアウト連携を求めており、さらにClient側で最長2週間のログアウト連携有効期間を要求していることになります。
Serverはこれらの値を受け取った時、client_idなどを利用して現在のログインセッションとClient情報を紐づけます。
レスポンスというかアサーションには以下のパラメータを含みます。

"ns.ssout":"http://openid.net/specs/ab/1.0#singlesignout",
"ssout.expires_in":"1209600"

意味はリクエストと同じです。
このアサーションを受け取った時、Clientは現在のログインセッションにServer識別子(server_id?)を紐づけます。
同じことを複数のClientが行うと、Server側はセッションに紐づけるclient_idが増えます。

Serverから始まるログアウト処理

Server側でユーザーがログアウト処理を行うとき、セッションに紐づいているClientについてログアウト処理を行わせなければいけません。
Serverは「セッションに紐づいた全てのClient」の「ログアウトエントリポイント」の一覧を「いわゆるWebビーコン」として画面に表示します。
ここでは3つのClientがログアウト連携をしていた場合の例をあげておきます。

<img border="0" width="1" height="1" src="http://client.example.com/logout?server_id=xxxxxxxxxx">
<img border="0" width="1" height="1" src="http://client2.example.com/logout?server_id=xxxxxxxxxx">
<img border="0" width="1" height="1" src="http://client3.example.com/logout?server_id=xxxxxxxxxx">
Clientから始まるログアウト処理

Client側でユーザーをログアウトさせたいとき、Server側にログアウト処理を依頼する必要があります。
Clientは自らのログアウト処理を行った後、以下のようなURLにユーザーを送ります。

http://server.example.com/logout?client_id=yyyyyyyyyy&redirect_uri=http%3a%2f%2fclient2%2eexample%2ecom%2flogout_callback
  • client_id : 必須?
  • redirect_uri : オプション。ログアウト画面を一瞬表示したりしたあとにClientに戻り先を指定します。

redirect_uriがないときは、ログアウト処理のあとにXRDSにあった#singlesignout_callbackに戻します。
ログアウト処理を行うときはさっきと同じです。
リダイレクトさせてきたClientは戻してログアウトさせるので、ビーコンでログアウトさせるのは不要だと思います。
なのでこんな感じですね。

<img border="0" width="1" height="1" src="http://client2.example.com/logout">
<img border="0" width="1" height="1" src="http://client3.example.com/logout">

この画面を表示もしくはJavaScriptを使って一瞬表示させたりなんかしたあとに、Clientに戻ります。
で、ユーザーを送ってきたClientは戻ってきたらにログアウト処理をすればいいんじゃないですかねという話です。

課題というか決まってないこと

Clientからのログアウト処理のときのServerのログアウト画面の出し方が微妙に難しいかもしれません。
JSでごにょごにょやったりとか。

Server側がダウンしてるとえらいことになります。
でも、Clientがそれを判断できるかというと。。。

逆もしかりです。
そもそも、Clientのエントリポイントが絶対に落ちないのであれば、Webビーコンの画像じゃなくて単純なリダイレクトでつなぐことも考えられます。
が、Clientがダウンした日にゃ。。。
twitter以外のServerはだいたいClientより安定してますよね。なので、今回はビーコンを提案してみましたよ。
まぁ、このビーコン方式だと「ClientがServerでもあって、いくつかのClientがぶら下がっている」なんてときに対応できないかもしれません。

まとめ

ダラダラと書いてみましたが、どうでしょうか?
図がないとわかりづらいと思いましたが、めんどくさいので文章だけにしておきます。
OAuth 2.0ベースのOpenID Artifact Bindingにかぶせたので難しそうに見えたかもしれませんが、そんなに難しい話ではありません。

日中は別のことばっかり考えていますが、まだまだIdentityのことで今まで考えたことは残っているので、少しずつまとめなおしてたまに書いていきますよ。
ではまた。