Skip to content

Instantly share code, notes, and snippets.

@mala
Last active December 15, 2015 13:39
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mala/5268998 to your computer and use it in GitHub Desktop.
Save mala/5268998 to your computer and use it in GitHub Desktop.
データをpostMessageで受け渡すセッション限りのトークン取得(取得しない)フロー

一瞬popup + 以降iframeでproxyするようなもの。XHR level2いらないのでやや動作環境が広がる。

  • クライアント側ボタンクリックで window.open + ランダムなid(これをclient_id相当にする)の名前をつけてiframe埋め込み
  • popup windowにiframeの名前をpostMessageで送る
  • サーバー側: popup windowはiframeに対してpostMessageで返信(event.source.frames.xxxx)、api-domainのoriginであることを確認、cookieで認証してランダムなidとセットで使えるトークン発行
  • トークン保存はmemcachedなど揮発性のストレージで良い。使っている限り期限が延長される。最長期限があってもよい(あったほうがよい)
  • ログアウトとセットで破棄されるようになっていると良い
  • iframeはポップアップwindowからメッセージを受け取る。ランダムなid + トークンを使ってAPIにアクセスする。
  • iframeは親windowからメッセージを受け取る。あとはpostMessageでproxyしてXHRのリクエスト、レスポンスをやり取りする。
  • iframeは親windowからのメッセージであることをevent.originを使って検証する
  • トークンは親windowに渡ってはいけない。レスポンスだけ受け渡す。

図: http://cache.gyazo.com/feef74a84d49644470eba6cb7cb4aac5.png

iframe api-domain/proxy#client_id
popup auth-domain/authorize

popup windowは現状のブラウザの仕様上、ユーザー操作を起因にして開かないとまずブロックされる。ボタンやリンクを押す必要はあり。

親window自体が別サイトからpopup windowとして開かれて、ユーザーが操作して認可を与えた場合に、popup.frames[num].postMessageを使ってapi-domainにリクエストが送られてしまうと困る、api-domainは親windowからのmessageかどうかを検証する必要がある。

api-domainのoriginに不適切なページがあった場合に、例えばpostMessageで受け取ったデータを外部に漏らしちゃう、というようなものがあれば(XSSがある前提だとそもそも安全じゃないことになるが、こういったものだと単体ではクリティカルな脆弱性にならないはずのところ) tokenが外部に漏れてしまうことになる。何らかの方法でiframeがproxy用のURLであることの保証が出来ると良い。popup -> iframeはいきなりtokenを渡すのではなく、お互いにclient_idを知っているかどうかを確認してから渡す方が良いかも知れない。

XHR level2で直接リクエストしてレスポンスを受け取っているのか、postMessageを使ってiframe経由でリクエストしているのか、というのはライブラリレベルでは完全に隠蔽できるので、開発者は気にする必要がない。

@kazuho
Copy link

kazuho commented Mar 29, 2013

ウェブブラウザが提供するセキュリティモデルの内側で情報交換が完結しており、トークンがサードパーティアプリに漏洩しないので美しいと思います。細かな疑問として、

  • サーバアプリのサーバサイドで、client_idあるいはトークンを保存する必要はないのでは?
  • 極論すると、ユーザー認証用の cookie の内容をそのまま API アクセス時の認証キーとしてもいいのでは?
  • その場合、サーバアプリが送信するiframe<->popup間での通信を盗聴されないよう、認証情報はサーバアプリしかしらない共通鍵で暗号化する必要があるかも

@mala
Copy link
Author

mala commented Mar 29, 2013

元々サードパーティCookie無効状態でlikeボタン等をシームレスに動かすには?というのは考えていたので、そのへんの延長ですね。認証キーは何でも構わないです。セッションidそのまま使ったほうが確実にログアウトと連動するので分かりやすいですね。

「トークンがサービス運営者以外には決して渡らない」という設計なので、権限毎に予め用意されているトークンを使い回す、といったものでも大丈夫なんじゃないか、と考えて、それはそれで漏洩した場合のリスクが大きいな、と考えました。

漏洩するリスクは、http使ってて通信経路で、iframe用のドメインにpostMessageの内容を漏らしてしまうような不適切なページがあった場合、ということになります。

client_id的な要素を入れたほうが良いと思ったのは「ブラウザのメモリ上に保持しているclient_idが消え去ればそのトークンが無意味なものになる」ということをうまい具合に保証できないものか、ということを考えたためです。

@mala
Copy link
Author

mala commented Mar 29, 2013

postMessageは送る際に「指定したoriginにのみ届くようにする」 受け手側も「指定したoriginから送られたものか確認する」というのができるので、ちゃんと指定/検証してれば別のドメインに送っちゃうということは無いです。postMessage使えないブラウザ向けの代替手段ではそういうの起こりそうで怖いですが。

@mala
Copy link
Author

mala commented Mar 29, 2013

わかりにくいのでちょっとまとめると。

  • 通常のケース: クライアントが信用出来ない外部jsを読み込んでいても、それも含めてユーザーが信頼して認可する。
  • このフローの場合: トークンを外部に渡さずその場限り、ということを前提にしてユーザーが認可するので、いかなる手段を使っても認証キーが外部に漏れることがあってはいけない。

postMessageは安全か?

  • api.example.com/proxy には当然に外部jsを含めないようにする
  • api.example.com の全てのページに外部jsが含まれていないのであれば、そのドメインにしかpostMessageの内容が渡らないことが保証できる
  • originの制限だけでは、クライアント側の操作で同一originのiframeに差し替えるということが出来てしまう
    • この形式のフローを作る際には、そのドメインには外部jsを一切入れるな、ということになってしまって敷居が高くなってしまう。
    • Implicit Flowでlocation.hashの値をアクセス解析jsが記録してしまうような状況と似ている。
    • アクセス解析入れていても redirect_uri の指定先のみ外部jsを外すことで、そういったことが起こらないことは(おおむね)保証できる。
  • api.example.com/proxy には外部js入れないよ、程度のことで、postMessageの内容が外部には漏れないことを保証したい。

apiとauthは同じドメインにしてevent.source.location.hrefを見るのが確実か。

@stomita
Copy link

stomita commented Mar 29, 2013

これ、大前提としてこのAPI認可形式のプロトコル化(+JSライブラリの提供)を目指している、ということでいいのかな?

で、その上でコメントなんだけど

apiとauthは同じドメインにして

AuthzのドメインはCookie食ってるだろうし、トークンで識別する限りCookie必要ないだろうからAPIとは分けたほうがいいんじゃないのかなー。特に、ドメイン同じであることを前提としてしまうと、今現在そうなっていないAPIサービス(結構あるよね)にとっては本方式の採用にちょっと負荷がかかるかも。

api origin にproxy以外のページがあるケース、というのが、正直ほんとにあり得るのか良くわからないし、そこは実装者に対して「そんなページ作るな」じゃだめなのかな? apiドメインにjson/xml以外返すサービス入ってくることってどれくらいあるだろう? IEでHTMLとして解釈されちゃう脆弱性とかまだ影響するんだっけ?

あと

originの制限だけでは、クライアント側の操作で同一originのiframeに差し替えるということが出来てしまう
この形式のフローを作る際には、そのドメインには外部jsを一切入れるな、ということになってしまって敷居が高くなってしまう。
Implicit Flowでlocation.hashの値をアクセス解析jsが記録してしまうような状況と似ている。
アクセス解析入れていても redirect_uri の指定先のみ外部jsを外すことで、そういったことが起こらないことは(おおむね)保証できる。

この項目の言わんとすることがまだよくわかってないかもしれないけど、親ウィンドウには外部スクリプト(悪さする可能性がある)が混入する可能性があるとした時、その場合でもスクリプトからtokenにアクセスできないようにしたい、要は何が何でも親ウインドウはtokenを見ることはできない、って状況を達成したい、ということでいいのかな?
これに対しては、先に書いたとおりapiドメインにproxy以外のページ置くな、が一番しっくりくるのだけど、そういう状況って(apiドメインにページが作られる状況)あるだろうか?

@stomita
Copy link

stomita commented Mar 29, 2013

そもそも親ウィンドウが外部スクリプトに乗っ取られているなら、tokenとるまでもなく認可後は外部からAPI叩き放題じゃん、って思ったのだが、どうなんだろう?そこらへんがいまいちしっくりこなかった。生tokenとられる方が被害が大きい、ってことなのかな?
あー、ユーザ認証用CookieをAPI tokenに使いまわす場合は問題か。だとするとやっぱりCookie使い回しを避けるほうがいいんじゃないかな。

@mala
Copy link
Author

mala commented Mar 29, 2013

新規で作る場合は、api用ドメイン分けろ余計なページ作るなでFAです。既存の既に存在してるapiに対するproxyをさっくり作りたい場合に、余計なこと考えなくても済む方が嬉しい。例えばcallback url使うものも同一ドメインにリダイレクタがあったら、とか考えないで済むに越したことない。

クライアント(親window)は信頼できない前提でOK。やりたいことはブラウザ閉じたりAPI提供サイトからログアウトした際に自動で認可も取り消される、というものです。

@stomita
Copy link

stomita commented Mar 29, 2013

あー、tokenとられない限りAPIを利用できるのはブラウザを開いている間で限定できるけど、tokenが外部に漏れたら際限なく使われてしまう(APIを使っている限り期限が延長される、とした場合)という事ですね。

@stomita
Copy link

stomita commented Mar 29, 2013

apiドメイン内に悪意スクリプト入り込む場合については、それを仮定するとすべてアウトのような気がするんだけど、どうかなー。api側のlocation.hrefチェックのためにapiとauthzのドメイン一緒にしたらそれこそauthzの認証Cookieも取れちゃう可能性あるだろうし、やっぱり安全に渡せる気がしない。

@kazuho
Copy link

kazuho commented Mar 30, 2013

@stomita

apiドメイン内に悪意スクリプト入り込む場合については、それを仮定するとすべてアウトのような気がするんだけど、どうかなー

僕は引用に同意見です。逆の言い方をすると、API用ドメインを分けるのではなく認証用cookieにアクセスできるドメイン上で、このAPIを提供すべき。

@kazuho
Copy link

kazuho commented Mar 30, 2013

@stomita

これ、大前提としてこのAPI認可形式のプロトコル化(+JSライブラリの提供)を目指している、ということでいいのかな?

言うの忘れてたのですが、図で認可リクエストが親ウィンドウからポップアップに行くことになってますが、親ウィンドウはiframeとのみ話し、ポップアップはiframeが生成しiframeとのみ通信するようにできないでしょうか? 目的は設計をシンプルにすることです。

親ウィンドウのやることが、iframeを生成し、そのiframeにpostMessageでリクエストを送信し、iframeからpostMessageでレスポンスを受信する、という作業のみに限定できれば、JavaScriptライブラリを提供する必要もなくなると思うのです。

@stomita
Copy link

stomita commented Mar 31, 2013

@kazuho

逆の言い方をすると、API用ドメインを分けるのではなく認証用cookieにアクセスできるドメイン上で、このAPIを提供すべき。

これについては、今回この提案方式をどういうケースを優先とした設計にしたいかによると思っていて、もし既存のAPIサービスに対してアクセス許可レイヤーを提供することが主目的ならば、すでにAPIドメインが認証Cookieの有効なドメインと分かれている場合は同一ドメインが仮定されてしまうと本方式の利用が難しくなってしまう、という懸念があります。実際、OAuth2ベースの多くのAPIサービスはそのようになっているように思います。

一方、すでにあるAjax XHRで作られてるCookieで認証しているWebアプリで内部APIを作っているようなケースで、そのAPI利用を外部に開放する場合についての活用用途を求めるならば、@kazuho さんの提案は比較的改変の負荷が低い(サーバ側でtokenの管理を新たに加えるといった必要がない)という点で受けいられる可能性が高いと考えられるかもしれません。

個人的な意見としては、必要な前提条件が増えることによって採用の可能性が減ってしまうのはもったいないとは思う一方、もともとがOAuth2などと比べて限定的なシーンの解決(しかしながらより実装者の負担が低くなるような)を目指しているのであろうので、適切な前提はやはり必要なのだろうな、とも思っています。なので、今回のこの提案でどちらを目指したいか、目指すべきであるかについて @mala さんの意見が聞きたいところです。

@stomita
Copy link

stomita commented Mar 31, 2013

@kazuho

言うの忘れてたのですが、図で認可リクエストが親ウィンドウからポップアップに行くことになってますが、親ウィンドウはiframeとのみ話し、ポップアップはiframeが生成しiframeとのみ通信するようにできないでしょうか? 目的は設計をシンプルにすることです。

これはポップアップウィンドウをiframe内から出すとすると、iframe内のコンテンツへのクリックが必要となるため、UIの設計に制約が多くなる、ということを @mala さんがツイートしていた気がします。

@mala
Copy link
Author

mala commented Mar 31, 2013

@kazuho @stomita

前提としてはauthとapiが同一ドメインのケースも、別ドメインのケースも想定しています。どっちかというと既存のサービス内部で使ってるようなapiへのproxyを提供する用途がメインのつもり。

これはポップアップウィンドウをiframe内から出すとすると、iframe内のコンテンツへのクリックが必要となるため、UIの設計に制約が多くなる、ということを @mala さんがツイートしていた気がします。

そうですね、リビジョンに残ってます。
"クライアントに悪意があれば返信が来るまえにiframeを別ページに差し替えることが出来る。これを防ぐにはiframeから直接認可windowを開かないといけない、そうなるとUIの幅が狭まる。iframeが差し替えられていないことの保証を入れたい。"

iframeにフォーカスが当たった状態で何らかの操作をしなければwindowが開けないはず、もし開けるとしたらそれは迂回手段になってしまう。ユーザー操作起因で発火されたpostMessage、を受け取ったmessageハンドラ経由でのwindow.open、なんてブラウザは判別できなくていいだろう。既にiframe内にボタンが表示されているようなものであれば、それをクリックすればよいけれど、そうでないものは対応できなくなってしまう。

なのでwindow開く部分はあくまでも親windowから呼び出すのが適切かなあ、と。

それから自分が想定しているのは同一originに悪意のあるスクリプトが紛れ込んでいる、という話とはちょっと違ってて。"XSSがある前提だとそもそも安全じゃないことになるが、こういったものだと単体ではクリティカルな脆弱性にならないはずのところ" と書いてるとおり、単体では大して危険ではないものが、組み合わせでリスク増加することになってはいけない、と考えている。

例えば、

  • URLに機密情報が含まれない前提で外部のアクセス解析サービスを使っていたところ、URLにパスワードが含まれては困る、とか
  • OpenIDやOAuthのcallback先のドメインにリダイレクタがあると安全に使えなくなってしまうようなもの。
  • jquery mobileやturbolinks、同一originのコンテンツが安全であることを暗黙のうちに求めてしまっている。

同一originに存在しているコンテンツが「安全」であることを求めるのは良いのだけど、その基準が通常よりも厳しい「安全」になってしまうのは良くないだろうと。

つまりこのケースだと「同一originで第三者がonmessageイベントを監視していないことを暗黙の前提として求める」のはダメで、そういうのがあっても安全にする方法を考えるか、あるいは明確にその求めている前提条件が分かるようにする。そしてAPI提供者やライブラリ利用者が余計なことを考えなくて済むように当然前者のほうが望ましい、と。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment