Webのユーザ認証とSSO (featuring facebook)
Webのユーザ認証とSSOに関する技術メモです。facebbook、OAuth、Google AuthSub、OpenSocial、OpenIDなど。
まず前提です。ユーザ認証はパスワード認証に限定します。またいわゆるフォーム認証を仮定します。モデル的にはパスワード認証やフォーム認証に限定する必要はありませんが、Webの世界では現実的にこのスタイルに限定されているのでこうします。この限定をせずに表現を抽象化すると、ユーザIDの代わりに認証子、パスワードの代わりに秘密情報とかになって百害あって一利無しだからです。
シナリオは、新しいWebアプリを作った開発者がいるところから始めます。開発者AさんがWebアプリを作りました。そのWebアプリはユーザにログインしてもらう必要があります。
目を転じるとWeb上には既に巨大なユーザアカウントを抱えたWebアプリが存在しています。一般的な定義とは異なりますが、巨大なユーザアカウントを抱えるWebアプリをここではSNSと呼ぶことにします。この定義に従うと、mixiみたいなものだけではなく、blogサービスでもAmazonでもGoogleカレンダーでもSNSです。用語の使い方が気持ち悪ければ、以下、SNSを単に新しく定義した記号と思ってください。
登場人物が揃ったので列挙します。
- 開発者Aさん
- 開発者Aさんの作ったWebアプリX
- (巨大なユーザアカウントを抱える)SNS
- SNSのエンドユーザたち
さて、開発者Aさんは、新しいWebアプリXのユーザを獲得したいわけですが、自前でユーザ認証を実装すると次のような苦労があります。
- セキュリティ的に穴の無いユーザ認証システムを作るのは色々と面倒
- ユーザのパスワードを保持してしまうと気をつかう
- ユーザ管理はそもそも面倒
- ゼロからユーザを集めるのも大変
SNSにユーザ認証を任せて、認証済みのユーザに自分のWebアプリを使わせると、上の問題がすべて解決します。 エンドユーザから見ても、わざわざ新しいサービスを試すたびにユーザ登録をする手間が省けます。SNSはユーザ囲い込みに使えます。それぞれの思惑が合致します。 いわゆるSSO(Single Sign On)です(SSOを実現する形式で分類すれば、これから説明するSSOはリバースプロクシ型SSOの一種です)。
この時、技術的に満たすべき要件は次のようになります。
- 絶対ルール
- エンドユーザは、SNSとだけパスワードをやりとりします
- エンドユーザは_決して_、WebアプリXとパスワードのやりとりはしません
このルール下で、WebアプリXは、エンドユーザがSNSによって認証済みかどうかを知りたいとします。
これを満たす技術は今に始まったわけではなく、クラシックなKerberosと基本的に同じモデルで実現できます。Web上ではTypeKeyが一番有名でしたが、ここ数年、facebookやGoogle Account APIなども登場しています。
派生形を無視して、通信フローを単純化すると次のようなやりとりをします。余談ながら、この手の絵でHTTPのリダイレクトのレスポンスを省略して、そのまま別サイトに飛ばす絵がありますが、個人的に好きになれません。以下の絵はリダイレクトの省略はしていません。リクエストとレスポンスの区別は自明かもしれませんが、ひとめで分かるようにリクエストは --> 、レスポンスは ==> にしています。
エンドユーザ WebアプリX SNS -------> <======= redirect to SNS login ---------------------------> <=========================== ログイン画面 ---------------------------> ユーザIDとパスワードをPOST <=========================== 認証成功 redirect to X's callback-URL + ticket等(as queryパラメータ) -------> callback-URL(=WebアプリXがあらかじめSNSに教えているURL) (with ticket as queryパラメータ) ---------> ticket等をPOST <========= 認証済みユーザのユーザ情報など <======= 認証済みユーザとしてサービス利用開始
実際にはセキュリティ上、考慮すべき点が多々あります。例えばreplay攻撃への対策として時刻情報を混ぜたり、また一般的にはWebアプリXとSNSの間であらかじめ秘密情報を共有しておきます。またWebアプリXはユーザをSNSのログイン画面に飛ばしたり、ticket等をPOSTするURLを知っていますが、これらはSNSがWeb APIとして公開している前提です。以下、「Web API」という用語をかなり広義に使っています。意味づけまで含めてURLを(プログラマに)公開したら、それはWeb APIと呼びます。
SNSが上のような仕組みを用意してくれていれば、開発者Aは、自前でユーザ認証を実装する代わりに、上のようなプロセスを実装することになります。ほとんどの場合、API(プログラミング言語で呼び出すAPI)が提供されていて、上のような通信フローを隠蔽しています。
ここまでで、開発者Aは面倒なユーザ管理から開放されました。WebアプリX内では、SNSが割り当てた(たいていは整数値の)ユーザIDでエンドユーザを識別すればよいだけです。
まだ話は続きます。Google Calendar、facebook、OpenSocial等では更にふたつの仕掛けがあります。上図のSNSがGoogle Calendarやfacebookに相当すると思ってください。
仕掛けのひとつ目は、上図の認証フロー後、WebアプリXがSNSに対して、エンドユーザの権限のまま振る舞う点です。通常、エンドユーザがSNSにログインしてからブラウザでやるような作業を、WebアプリXがそのユーザの権限で行ってしまいます。WebアプリXがSNSからGETでデータを引っ張ってきたり、POSTでデータを書き換えたりできます。これらをエンドユーザの権限で行います。
この時のGETやPOSTのやりとり(URL定義とレスポンス定義)が、「ユーザ認証を必要とする、サーバ間で使われるWeb API」です。AuthSubを使ったGoogleのWeb APIが代表的です。
WebアプリXは悪意があるかもしれません。秘密のカレンダーを読み出して、公衆にさらすかもしれません。なので、エンドユーザはWebアプリXにどこまで許すかを設定する必要があります。この設定はあくまでSNS上で行う設定です。WebアプリXはエンドユーザから許可を得られれば、SNS上の秘密のカレンダーを取得することができます。このモデルでは、この設定を「ユーザ認可」と呼べます。取得を許してしまえば、その情報をどうするかはWebアプリXの世界なので、SNS上の設定では関与できません。
エンドユーザはWebアプリXを信用できるかを見極める必要があります。SNSを信用するかはこの文脈ではあまり意味がありません。なぜなら、秘密のカレンダーを作っている時点で、信用しているからです。
ここまでの話をまとめると、
- ユーザ管理やユーザ認証が面倒なので、既存SNSに丸投げして楽をした
- ついでに、エンドユーザが許す範囲で、エンドユーザの権限でSNS上のデータを読み書きできるようになった
ということです。言うまでもありませんが、SNS側が、こういうことのできる仕組みを提供してくれたら、というただし書きつきです。
facebookやGoogle Calendarはこういうことができるわけです。
facebookの場合、もうひとつ面白い点があります。facebookの画面内にWebアプリXの画面を埋め込めることです。埋め込むパターンはふたつあって、ひとつはcanvasと呼ばれ、もうひとつはprofileと呼ばれています。後者はGoogle的にはgadgetsです。前者はfacebookのアプリ領域画面全体をWebアプリXが奪うイメージです。canvasへの埋め込みには、iframeもしくはreverse proxy的な動作の2種類あります。facebookでは後者(reverse proxy的動作)をFBMLと呼んでiframe型と区別しています。
上図と同様の動作(つまりエンドユーザが最初にアクセスするのがfacebookアプリXで、そこからfacebookのユーザ認証画面に飛ばす)のfacebookアプリXも可能ですが、一般には、エンドユーザは最初にfacebookにログインして、その後、ナビゲーションメニューからfacebookアプリを呼び出します。この辺りはまた別の機会に書きます。
facebookは技術的に稚拙と思える部分も多々あります。例えば、Web APIは http://api.facebook.com/restserver.php に対して常にPOSTメソッドで行い、かつ意味的な区別はパラメータのmethodの値で行います。例えば、http://api.facebook.com/restserver.php に method=events.get をPOSTすればイベント取得を意味する、といった動作です。これでRESTと主張しています。彼らの中では、URL叩いてレスポンスがXMLならRESTなのでしょう。しかし、技術のブレークスルーには、ある種のおおらかさと言うか、アナーキーさと言うか、そういうものが必要なのかもしれません。facebookを見ているとそう感じます。
ここでようやくOpenSocialの登場です。実はOpenSocialの存在が微妙です。上のモデルを標準化しようという動きなら分かりやすいです。OpenSocialはそういう側面も持っています。http://code.google.com/apis/opensocial/docs/index.html でRESTful Data APIsと呼んでいる部分です。facebookと違ってGoogleはきちんとRESTする会社です。上図のフローで言うと、ユーザ認証とユーザ認可をOAuthとAuthSub(Google Account API)で行い、データアクセスをAtomPub(Google的にはGData)で行うはずです。はず、と書いたのはRESTful Data APIsの情報がまだ不明瞭だからです。OpenSocialでは、なぜか(ぼくから見ればあまり本質とは思えない)gadgetsのスペックばかり決めています。RESTful Data APIsと呼んでいる、上図のフローの方が重要だと思うのですが。
OpenSocialにもひとつ良い点があります。「Hosting OpenSocial Apps」(http://code.google.com/apis/opensocial/container.html)です。ここまで、SNSは与えられたもの、という感じで書いてきましたが、自分で作っていけない法はありません。「Hosting OpenSocial Apps」はSNSを作る側の話です。
この文脈ではOpenIDにも触れておくべきでしょう。ここまでSNSが一括してユーザ管理とユーザ認証を行う中央集権モデルを前提にしてきました。ここでのSNSの定義のもとで言えば、OpenIDはオレオレSNSを作っているようなものです。モデル的には、WebアプリXは決まったSNSではなく、エンドユーザが提示するOpenIDサーバ(IdP)とやりとりをします。OpenIDの場合も最初に書いた「絶対ルール」は成立します。OpenIDに対応したWebアプリXは、ユーザ管理機能らしきものは必要になる気がしますが、少なくともパスワード管理からは解放されます。