フロントエンドWeb戦略室

第3回 localStorageとpostMessageの使いどころ(2)

この記事を読むのに必要な時間:およそ 5 分

postMessage

一般的に広く使われている,URLの?以降の文字列(query string)を使いサーバに対してデータを受け渡す方式は,異なるドメインのJavaScript同士で通信する際にはいくつかのデメリットがあります。http://example.com/?query_stringというURLにアクセスするとquery_stringの部分がサーバに送信されます。当然新規の通信が発生しますし,どのようなメッセージが送信されたのかをJavaScriptから受け取るには,サーバがブラウザに対して応答を返すまで待たなければなりません注3)。postMessageの登場以前も,サーバサイドを経由しない,JavaScriptだけで完結するクロスドメインでのメッセージ送信手法が考えられてきました。代表的なものは,window.name注4を使った方法リスト1とlocation.hashを使った方法リスト2です。

リスト1 window.nameを使ったメッセージ送信

<iframe src="http://example.com/" name="message">

リスト2 location.hashを使ったメッセージ送信

http://example.com/#location.hash
注3)
たとえば1秒間に30回メッセージを送りたい,といったケースでは使えないことになってしまいます。
注4)
window.nameは元々は,リンクをクリックした際やフォームの送信時,指定したフレームやポップアップウィンドウで開くようにブラウザに指示するためのものです。たとえば,<a href="…" target="right_frame"> … </a>といったリンクを作ることで,right_frameという名前のついたフレームでリンク先が開かれることになります。nameを指示したうえでwindow.openによるポップアップを作成したり,name属性をつけてiframeを読み込むことで,開いたファイルが異なるドメインであっても,指定したウィンドウの名前がwindow.nameに格納されることになります。

postMessage登場以前

リスト1のname="message"の部分はJavaScriptの変数window.nameとして読み込まれたiframeから参照することができます。この際にwindow.nameはサーバに送信されることがありません。また,URLの#以降,location.hashやハッシュフラグメントと呼ばれる部分も,自動的に送信されることがありません。query stringを使う方式と違って,一度キャッシュしておけばサーバとの通信が発生せず,オフラインの状態であっても,JavaScriptから送られてきたメッセージを参照することができます。ブラウザにキャッシュ済みのURLを,window.nameやlocation.hashを変化させて読み込むことで,サーバとの通信を発生させずに,相手先ドメインのJavaScriptに変数を受け渡すことを可能にしているのです。送信元ドメインのiframeを読み込んで,同様にlocation.hashやwindow.nameを設定することで,受け取ったメッセージに対する返信を行えます。こういった旧来のクロスドメインメッセージング手法は,複数のiframeをネストさせて読み込む必要がありますし,さまざまなブラウザで動作させるためにコードが複雑になりがちでした。postMessageはこの問題を解決してくれます。

postMessageの誕生

HTML5でpostMessageが導入されたことで,JavaScriptだけで完結するクロスドメインの送信手段が劇的に改善しました。MDNでは,

window.postMessageは,安全にクロスドメイン通信を可能にするためのメソッドです。

と紹介されていますが,実はpostMessageはクロスドメイン通信のみならず,さまざまなシーンで汎用的に使われるメッセージングのしくみと考えることができます。postMessageがサポートされているのは,Webページ内のwindowオブジェクトだけではありません。表示中のHTMLと別スレッドでJavaScriptを実行するWeb Workersとの通信や,ブラウザ拡張機能との通信などさまざまなシーンで「相手を指定して,メッセージを送る」という機能がサポートされています。そして,多くの場合,postMessageと共通のAPI注5により実現されています。

注5)
たとえばGoogle Chromeの拡張機能用のAPIでは,Webサイトと拡張機能との通信や,拡張機能同士での通信がサポートされています。postMessageと多少の差異はあるものの,メッセージパッシングによるプログラミングモデルはpostMessageと共通になっています。

クロスドメインのためのAPI設計

写真サービスhttp://photo.example.com/ブログサービスhttp://blog.example.jp/があると想定します。それぞれのサービスは別組織が運営していて,ログイン情報は共有していません。ここで,ブログに貼り付ける画像を写真サービスから選択してみます。写真サービスにログイン中のユーザのアルバムから,画像URLをもらってくることにします。

ダメな例:パスワードを預かる

photo.example.comのユーザidとパスワードをblog.example.jpが預かって,代理でアルバムの写真の一覧を取得できるようにします。しかしこれではblog.example.jp側のミスでパスワードが漏洩(ろうえい)するリスクがありますし,悪用されても検知できません。

ダメな例:JSONP

ブラウザから直接photo.example.comのデータを参照します。http://photo.example.com/api/album/listというAPIを用意して,JSONPで写真の一覧を返すようにするとしましょう。

  • ログイン中のCookieを送ることでユーザを識別する
  • JSONPもしくは,CORSCross-Origin Resource Sharingにより許可されたXMLHttpRequestでリソースを取得する

こういったAPIを作ってしまうと,ログイン中のユーザidであったり保存している写真の一覧を誰でも取ることができてしまいます。

APIを用意してJSONPで写真の一覧を返す方法についてもう少し深く,photo.example.comblog.example.jp「全面的に信頼する」という前提で,blog.example.jpからのみ,APIの呼び出しを許可することを考えてみましょう。

JSONPは呼び出し元を制限することが困難です。リファラを参照することで想定と異なるドメインから呼び出されたことを検出できますが,リファラは送信が必須のヘッダではありません。ブラウザの設定やセキュリティソフトによっては送られないこともありますので,特定のドメインから呼び出されたということを確実に判定することができません。クロスドメインのXMLHttpRequestの場合,originヘッダを参照することで,特定のドメインからのみクロスドメイン通信を許可することができます注6)。しかしアクセス元を制限したとしても,やはり問題は残ります。ユーザがサードパーティCookieを無効化していた場合は,XMLHttpRequestの設定よりも,ユーザの設定が優先されることになります。つまり,ログインCookieをそのまま使ったJSONP APIを作ってしまうことは主に2種類の問題があります。1つは本来ユーザの許可が必要なデータを不用意に返してしまうなど実装ミスを引き起こしやすいこと。2つ目はユーザがブラウザの設定を変更していた場合注7),正常に動作しなくなることです。

良い例:OAuthを使う

初回アクセス時,photo.example.comにリダイレクトしてphoto.example.comblog.example.jpに対し,「写真の読み取り権限を与えて良いですか?」とユーザに対して確認をするのは良い方法でしょう。ユーザが許可した場合,アルバムに保存してある写真の一覧にアクセス可能,という権限を与えます。http://photo.example.com/api/album/listに対するJSONPやクロスドメインXMLHttpRequestのリクエストに,アクセストークンを付加することで「どのサービスからの呼び出しであるのか」「誰がリクエストしているのか」を判別できるようになります。

注6)
またwithCredentialsプロパティをtrueに設定することでCookieを送信することもできます。
注7)
リファラの送信を無効化している場合や,サードパーティCookieを無効化している場合を指します。

著者プロフィール

mala(マラ)

NHN Japan所属。livedoor Readerの開発で知られる。JavaScriptを使ったUI,非同期処理,Webアプリケーションセキュリティなどに携わる。

Twitter:@bulkneets

コメント

コメントの記入