プリフライトと認証情報付きリクエスト
文書はhttp://127.0.0.1:4322、APIは4323番です。PATCHとJSON本文・X-Labヘッダーの事前確認を調べ、次にcredentialsの値とサーバーの許可設定を変えます。同じホストのCookieはポート別ではなく、ここではlab=blueが保存されています。
説明
別オリジンへの要求では、メソッドやヘッダーによって本要求の前にOPTIONSの確認が行われます。このプリフライトが成立しなければ、本要求は送信されません。Cookieなどの資格情報を含む場合は、応答公開にも追加の条件があります。
文書はhttp://127.0.0.1:4322、APIは4323番です。PATCHとJSON本文・X-Labヘッダーの事前確認を調べ、次にcredentialsの値とサーバーの許可設定を変えます。同じホストのCookieはポート別ではなく、ここではlab=blueが保存されています。
1事前確認が必要な要求
PATCHとJSON本文を送る要求を、許可なしのAPIへ出します。
(globalThis.api="http://127.0.0.1:4323",await fetch(api+"/api?policy=deny&tag=patch-deny",{method:"PATCH"事前確認が必要なメソッド,headers:{"Content-Type":"application/json"JSONの要求形式,"X-Lab":"one"独自の要求ヘッダー},body:'{"qty":2}'}).then(r=>({status:r.status}),e=>({error:e.name})))この要求はプリフライトが必要で、許可がないため失敗します。次に、本要求が送られたかを記録で確認します。
2OPTIONSだけが届く
patch-denyの受信記録を表示します。
await(await fetch("/records?tag=patch-deny事前確認と本要求の記録")).json()OPTIONSだけが届き、Access-Control-Request-MethodにPATCH、Request-Headersにcontent-type,x-labが入ります。これは送りたい本要求の条件です。許可が成立しなかったためPATCH本文は届いていません。
3メソッドとヘッダーを許可する
指定オリジン・PATCH・Content-Type・X-Labを許可したAPIへ同じ内容を送ります。
await(await fetch(api+"/api?policy=exactオリジン・メソッド・ヘッダーを許可&tag=patch-allow",{method:"PATCH"許可後の本要求,headers:{"Content-Type":"application/json","X-Lab":"one"},body:'{"qty":2}'})).json()本要求が成功し、PATCHとJSON本文が返ります。Allow-Originだけでなく、必要なメソッドと要求ヘッダーもプリフライトで許可されています。
4確認の後に本要求が続く
patch-allowの二つの要求を表示します。
await(await fetch("/records?tag=patch-allow二つの要求を順に取得")).json()OPTIONSの後にPATCHが記録されています。事前確認にはJSON本文はなく、許可された本要求に本文が入ります。この演習ではMax-Age=0と別のURLを使います。実際にはプリフライト結果をキャッシュできるため、毎回OPTIONSが見えるとは限りません。
5既定では別オリジンへCookieを含めない
通常のFetchで、応答の公開を許可したAPIへGETします。
await(await fetch(api+別オリジンへ既定設定で取得"/api?policy=exact&tag=default-cookie")).json()受信Cookieは空です。Fetchの既定はcredentials:same-originなので、ホストが同じでもオリジンの異なる4323番への要求には含めません。Cookie自体の対象範囲とFetchの資格情報設定は別の条件です。
6includeとAllow-Originの*
Cookieを含め、ワイルドカードの公開許可を返すAPIへGETします。
await fetch(api+"/api?policy=wildAllow-Originは*&tag=wild-cookie",{credentials:"include"資格情報を含める}).then(r=>({status:r.status}),e=>({error:e.name}))読取りは失敗します。credentials:includeの応答公開にAllow-Origin:*は使えません。Cookieが送られたかは次の記録で確認します。
7Cookieは送信済みである
wild-cookieの要求を取得します。
(await(await fetch("/records?tag=wild-cookie受信したCookieを確認")).json()).map(r=>({method:r.method,cookie:r.cookie}))GETにlab=blueが含まれています。このGETでは応答を読めないこととCookieを送らないことは一致しません。資格情報付き要求でも、事前確認を要しない場合があります。
8オリジン一致だけでは足りない
Allow-Originを一致させ、Allow-Credentialsを返さないAPIへ要求します。
await fetch(api+"/api?policy=exactオリジンだけを一致&tag=no-credentials-header",{credentials:"include"資格情報付き要求}).then(r=>({status:r.status}),e=>({error:e.name}))これも読取りに失敗します。資格情報付きの応答公開には、具体的なオリジンの一致に加えてAccess-Control-Allow-Credentials:trueが必要です。
9二つの公開条件を満たす
具体的なAllow-OriginとAllow-Credentials:trueのAPIへ要求します。
await(await fetch(api+"/api?policy=credsオリジンと資格情報の公開を許可&tag=credentials-ok",{credentials:"include"Cookieを含める})).json()応答が読め、lab=blueの受信も確認できます。これは同じホストでの例です。異なるサイトの場合にはSameSiteやブラウザのCookie制限も関係します。CORSの許可だけであらゆるCookie送信が可能になるわけではありません。
10資格情報付きPATCH
Cookieを含むPATCHを、必要な許可を返すAPIへ出します。
await(await fetch(api+"/api?policy=all&tag=credential-patch",{method:"PATCH"事前確認が必要な操作,credentials:"include"本要求に資格情報を含める,headers:{"Content-Type":"application/json","X-Lab":"two"},body:'{"qty":3}'})).json()今度はプリフライトを経て、Cookieと本文を含むPATCHが届きます。事前確認自身と本要求とで、Cookieの有無を比較します。
11プリフライトにはCookieを含めない
二つの要求のmethod・Cookie・本文を抜き出します。
(await(await fetch("/records?tag=credential-patch資格情報付き操作の記録")).json()).map(r=>({method:r.method,cookie:r.cookie二段階のCookieを比較,body:r.body本文の送信段階,requestMethod:r.requestMethod,requestHeaders:r.requestHeaders}))OPTIONSのCookieと本文は空で、本要求のPATCHにはlab=blueとqty=3が入ります。事前確認の許可と本要求の認証・認可は役割が異なります。サーバーではOPTIONSにCookieがないことを前提に確認条件を返し、本要求で操作の権限を検査します。
まとめ
プリフライトは本要求のmethodとヘッダーを提示し、許可された場合に本要求が続きます。
credentials:includeでは具体的なAllow-OriginとAllow-Credentials:trueが必要です。
プリフライトのCookieなしと、本要求の資格情報付き処理を分けて確認します。
操作履歴と解説を振り返れます。
ブラウザ / オリジン・通信記録
(globalThis.api="http://127.0.0.1:4323",await fetch(api+"/api?policy=deny&tag=patch-deny",{method:"PATCH",headers:{"Content-Type":"application/json","X-Lab":"one"},body:'{"qty":2}'}).then(r=>({status:r.status}),e=>({error:e.name}))){
"error": "TypeError"
}await(await fetch("/records?tag=patch-deny")).json()[
{
"tag": "patch-deny",
"method": "OPTIONS",
"origin": "http://127.0.0.1:4322",
"requestMethod": "PATCH",
"requestHeaders": "content-type,x-lab",
"cookie": "",
"body": ""
}
]await(await fetch(api+"/api?policy=exact&tag=patch-allow",{method:"PATCH",headers:{"Content-Type":"application/json","X-Lab":"one"},body:'{"qty":2}'})).json(){
"accepted": true,
"method": "PATCH",
"cookie": "",
"body": "{\"qty\":2}"
}await(await fetch("/records?tag=patch-allow")).json()[
{
"tag": "patch-allow",
"method": "OPTIONS",
"origin": "http://127.0.0.1:4322",
"requestMethod": "PATCH",
"requestHeaders": "content-type,x-lab",
"cookie": "",
"body": ""
},
{
"tag": "patch-allow",
"method": "PATCH",
"origin": "http://127.0.0.1:4322",
"requestMethod": "",
"requestHeaders": "",
"cookie": "",
"body": "{\"qty\":2}"
}
]await(await fetch(api+"/api?policy=exact&tag=default-cookie")).json(){
"accepted": true,
"method": "GET",
"cookie": "",
"body": ""
}await fetch(api+"/api?policy=wild&tag=wild-cookie",{credentials:"include"}).then(r=>({status:r.status}),e=>({error:e.name})){
"error": "TypeError"
}(await(await fetch("/records?tag=wild-cookie")).json()).map(r=>({method:r.method,cookie:r.cookie}))[
{
"method": "GET",
"cookie": "lab=blue"
}
]await fetch(api+"/api?policy=exact&tag=no-credentials-header",{credentials:"include"}).then(r=>({status:r.status}),e=>({error:e.name})){
"error": "TypeError"
}await(await fetch(api+"/api?policy=creds&tag=credentials-ok",{credentials:"include"})).json(){
"accepted": true,
"method": "GET",
"cookie": "lab=blue",
"body": ""
}await(await fetch(api+"/api?policy=all&tag=credential-patch",{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json","X-Lab":"two"},body:'{"qty":3}'})).json(){
"accepted": true,
"method": "PATCH",
"cookie": "lab=blue",
"body": "{\"qty\":3}"
}(await(await fetch("/records?tag=credential-patch")).json()).map(r=>({method:r.method,cookie:r.cookie,body:r.body,requestMethod:r.requestMethod,requestHeaders:r.requestHeaders}))[
{
"method": "OPTIONS",
"cookie": "",
"body": "",
"requestMethod": "PATCH",
"requestHeaders": "content-type,x-lab"
},
{
"method": "PATCH",
"cookie": "lab=blue",
"body": "{\"qty\":3}",
"requestMethod": "",
"requestHeaders": ""
}
]演習完了