一覧SD-11-2 · APIとブラウザ境界の防御

CORS・フレーム制限・HTTPS

ブラウザの許可とサーバー側の認証を別々に適用し、各ヘッダーが守る対象を説明します。

説明

ブラウザの防御には、別オリジンからの応答読取り、他ページへの埋込み、HTTPからHTTPSへの移行など、異なる対象を守る仕組みがあります。これらをAPIの利用者認証と混同しないようにします。

二つのオリジンを使い、CORSで読取りを許す場合と拒否する場合を比較します。次にiframeの埋込み可否を調べ、最後にHSTSの設定と受信条件を確認します。

ブラウザの許可とサーバー側の認証を別々に適用し、各ヘッダーが守る対象を説明します。

1二つのオリジンを確認する

ポートが異なれば、同じlocalhostでも別オリジンです。

console.log(location.originこのページのオリジンを読む); console.log(new URL(other).originAPI試料のオリジンを読む);

ホスト名が同じでもポートが違うため、fetchの応答読取りにはCORSの条件が関わります。

2CORS許可なしの応答を要求する

GETは相手へ届きますが、応答に許可ヘッダーがありません。

console.log(await readAPI(false, false)CORS許可と認証なしで要求する); console.log(await observed()API側の到達記録を読む);

TypeErrorで応答は読めません。相手の到達記録にはGETがあり、CORS拒否を通信自体の停止と同一視できません。

3CORSを許可しても認証なしでは401になる

許可オリジンを応答に付けますが、認証情報は送りません。

console.log(await readAPI(true, false)CORSだけを許可した応答を読む);

401とauthentication_requiredを読めます。CORSは応答をブラウザへ見せる条件で、APIを使う権限は与えません。

4認証付き要求とプリフライトを照合する

固定の公開Bearer試料をAuthorizationへ付けます。カスタムヘッダーの送信前にOPTIONSが発生します。

console.log(await readAPI(true, true)CORS許可と固定認証を併せて使う); console.log(await observed()事前要求と本要求の順序を調べる);

OPTIONSの後にGETが届き、200とpublic reportを読めます。認証とCORSの両条件が満たされました。

5iframeの埋込み可否を比較する

同じ子ページにframe-ancestors noneを付けた場合と、制約なしの場合です。子ページからのready通知を待ちます。

console.log(await frameTrial(true)埋込み可能な子の通知を確認する); console.log(await frameTrial(false)埋込み拒否された子と比較する);

許可側ではready通知を受け、拒否側では受けません。CSPは埋め込まれる子の応答ヘッダーに設定します。CORSの許可とは別の制御です。

6HSTSの設定と受信プロトコルを読む

HSTSはHTTPSで受信した設定を使って、以後のHTTPアクセスをHTTPSへ上げる仕組みです。この試料応答はHTTPです。

const response = await fetch("/hsts"); console.log(response.headers.get応答のHSTS設定を読む("Strict-Transport-Security")); console.log(location.protocol受信したページのプロトコルを確認する);

max-ageとincludeSubDomainsを持つ設定でも、HTTPで受信したヘッダーはHSTSの有効化には使いません。導入時には最初のHTTPS接続と、対象サブドメインのHTTPS対応も確認します。

7ブラウザ側の許可と利用者の認証を再確認する

CORSを許可したまま、認証情報を再び外します。

console.log(await readAPI(true, false)認証を外した要求をもう一度送る);

再び401です。CORS・frame-ancestors・HSTSの設定は、サーバーが行う主体と操作の認可を置き換えません。

まとめ

CORSは応答読取りの条件で、利用者の認証とは別です。

frame-ancestorsは埋め込まれる側が設定します。

HSTSはHTTPSで受け取る設定と対象範囲を確認します。

操作履歴と解説を振り返れます。

JavaScript / ブラウザ / localhostの異なる二ポート

1
console.log(location.origin); console.log(new URL(other).origin);
http://localhost:44719
http://localhost:44720
2
console.log(await readAPI(false, false)); console.log(await observed());
TypeError
["GET"]
3
console.log(await readAPI(true, false));
[401,"authentication_required"]
4
console.log(await readAPI(true, true)); console.log(await observed());
[200,"public report"]
["GET","GET","OPTIONS","GET"]
5
console.log(await frameTrial(true)); console.log(await frameTrial(false));
ready
no ready message
6
const response = await fetch("/hsts"); console.log(response.headers.get("Strict-Transport-Security")); console.log(location.protocol);
max-age=3600; includeSubDomains
http:
7
console.log(await readAPI(true, false));
[401,"authentication_required"]

演習完了