同一オリジンポリシー — ブラウザが最初から持っている壁
なぜ他のサイトの JavaScript から自分のページのデータを読めないのか。この制約がなければ Web はどうなっていたか。
この記事の進み方
What — オリジンとは何を指すか
オリジンは スキーム + ホスト + ポート の3点セットです。1つでも違えば別のオリジンになります。
- スキーム
- http か https か。同じホストでも、http と https は別オリジン。
- ホスト
- ドメイン名。app.example.com と api.example.com は別オリジン。サブドメインが違えば別。
- ポート
- 3000 と 8080 は別オリジン。省略時は http が 80、https が 443。
https://app.example.com を基準に、何が同じで何が違うかを見てください。
| 比較対象 | 同一オリジンか | 理由 |
|---|---|---|
https://app.example.com/other/path | ✅ 同一 | パスは判定に含まれない |
https://app.example.com:443/ | ✅ 同一 | https の既定ポートは 443 |
http://app.example.com | ❌ 別 | スキームが違う |
https://api.example.com | ❌ 別 | ホストが違う |
https://example.com | ❌ 別 | サブドメインの有無も別扱い |
https://app.example.com:8443 | ❌ 別 | ポートが違う |
何が禁止され、何が許されているか
別オリジンのリソースの「中身を読む」こと。JavaScript からの読み取りが対象。
// 別オリジンの iframe の中身
iframe.contentDocument // null
// 別オリジンへの fetch のレスポンス
await fetch('https://other.com/api') // CORS ヘッダがなければ読めない
// 別オリジンの Cookie / localStorage
// そもそもアクセスする手段がない- iframe の DOM
- 読めない
- fetch のレスポンス
- 既定では読めない
- localStorage
- オリジンごとに完全分離
- canvas に描いた別オリジン画像
- ピクセルを読めない
- 画像の表示
- —
- スクリプトの実行
- —
- フォームの送信
- —
- iframe での表示
- —
- –「読めない」だけで、リクエストが飛んでいないわけではない点が重要。
- –サーバー側の設定では解除できない。解除できるのは CORS など、ブラウザが用意した例外の仕組みだけ。
「読めない」が制約の中心。送ることや表示することは許されている。この非対称さを理解していないと、CSRF がなぜ成立するのかも分からなくなる。
iframe の contentDocument が null になります。サーバー側のヘッダ設定で解除できますか?
Why — この壁がなかったら何が起きるか
想像してみてください。同一オリジンポリシーが存在しない世界です。
あなたがネットバンキングにログインしたまま、別のタブで悪意のあるサイトを開きます。そのサイトの JavaScript は、こう書けます。
// もし同一オリジンポリシーがなければ、これが動く
const res = await fetch('https://bank.example.com/accounts');
const balance = await res.json(); // 残高が読める
sendToAttacker(balance);
const iframe = document.createElement('iframe');
iframe.src = 'https://mail.example.com/';
document.body.append(iframe);
// iframe.contentDocument.body.innerText で、メールの全文が読める
ブラウザは Cookie を自動で送るので、リクエストはあなたとしてログイン済みの状態で飛びます。残高も、メールも、SNS のダイレクトメッセージも、開いているだけで読み放題になる。
同一オリジンポリシーは、この破滅を防ぐためにブラウザが既定で持っている壁です。サーバーが何も設定しなくても、最初から有効になっている。
なぜサーバーの設定で外せないのか
この壁が守っているのは、サーバーではなく利用者です。
もしサーバー側の設定で外せるとしたら、攻撃者は自分のサーバーで「全オリジンからの読み取りを許可する」と設定するだけで済みます。しかし攻撃者が読みたいのは自分のサーバーのデータではなく、銀行のデータです。
だから、読み取りを許可できるのは読まれる側(銀行)だけであり、その許可を伝える仕組みが CORS です。そして許可がない限り、ブラウザは読ませません。
壁を越えるために用意された抜け道
読み取りが必要な正当なケースもあるので、ブラウザは限定的な例外を用意しています。
- CORS
- 読まれる側のサーバーが Access-Control-Allow-Origin ヘッダで「このオリジンには読ませてよい」と宣言する。最も一般的な手段。
- postMessage
- オリジンをまたいで、明示的にメッセージだけを送り合う。iframe や別ウィンドウとの連携に使う。DOM は触れないが、データの受け渡しはできる。
- document.domain
- サブドメイン間で壁を下げる古い仕組み。危険なため廃止が進んでいる。新規には使わない。
- CORP / COEP
- 逆に、埋め込まれる側が「このリソースを他オリジンから使わせない」と宣言する仕組み。壁を高くする方向。
冒頭の事故の正解は postMessage でした。iframe の DOM を直接触ることは原理的に不可能ですが、両者が合意してメッセージをやり取りする設計にすれば連携できます。
// 親から
iframe.contentWindow.postMessage({ type: 'fill', value: 'x' }, 'https://b.example.com');
// iframe 側
window.addEventListener('message', (e) => {
if (e.origin !== 'https://a.example.com') return; // 送信元の検証は必須
if (e.data.type === 'fill') input.value = e.data.value;
});
e.origin の検証を省くと、任意のサイトから操作できる穴になります。壁を越える仕組みを使うときは、越えてよい相手を自分で確認する責任が発生するということです。
同一オリジンポリシー下でも許されているものはどれですか?
演習 — まず自分で判断する
解説を読む前に、まず自分で判断してみてください。ここで一度詰まっておくと、 次の節の判断軸が「なるほど」ではなく「そう来たか」に変わります。
この構成のうち、ブラウザに止められるのはどれですか?
- 自社サイト https://shop.example.com に、次の要素を置こうとしている
- ① https://cdn.other.com/chart.js を script タグで読み込み、実行する
- ② https://api.example.com/orders に fetch でアクセスし、JSON を読む
- ③ https://blog.example.com を iframe で埋め、その中の要素の高さを JS で測る
- ④ https://payment.other.com/checkout へ form の POST で遷移する
- ⑤ https://img.other.com/logo.png を canvas に描き、ピクセルの色を取得する
- · オリジンの3要素(スキーム・ホスト・ポート)で、どれが一致しているか
- · その操作は「読み取り」か、「送信・表示」か
- · 同じ会社のドメインであることに、ブラウザは意味を見出すか
「iframe の中を直接操作したい」という要望に、なぜできないかと代わりの方法を説明してください
- 相手はサーバーの設定で何とかなると考えている
- 2つの社内システムを統合する場面
- 代替案まで示して前に進めたい
読み終わりましたか?
読了にすると、これを前提とする記事がロードマップで開放されます。