ローカル環境で2オリジンの環境を用意してcookieの挙動を調べる。自分用メモ
環境構築
1つ目の環境はreact create-react-appで作成し、yarn startでサーバーを立ち上げる
$ npx create-react-app react_test --template typescript
$ yarn start
import React from 'react';
import './App.css';
function App() {
const request = async () => {
const url = 'http://127.0.0.1:8000/api/hoge';
try {
const response = await fetch(url, {});
const json = await response.json();
console.log(json);
} catch (error: any) {
console.warn(error);
}
};
const onClick = () => {
request();
};
return (
<div className="App">
<button onClick={onClick}>ボタン</button>
</div>
);
}
export default App;
2つ目の環境はlaravelで作成し、php artisan serveでサーバーを立ち上げる
$ composer create-project laravel/laravel laravel_test
$ php artisan sever
# cookieにpiyoをつける
Route::get('/hoge', function (Request $request) {
$piyo = $request->cookie('piyo');
return response()->json(['piyo' => $piyo])->cookie('piyo', 'pppp', 100000);
});
シンプルにapi投げる
fetchのオプションなしでシンプルにapi投げる。
const url = 'http://127.0.0.1:8000/api/hoge';
try {
const response = await fetch(url, {});
const json = await response.json();
console.log(json);
} catch (error: any) {
console.warn(error);
}
結果
ただし、2回目apiなげてもcookieはセットされていない。
credentialsをincludeする
ブラウザーに認証情報の入ったリクエストを送るようにするには、オリジン間の呼び出しであっても、 credentials: 'include' を init オブジェクトに追加して fetch() メソッドに渡します。
https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch#sending_a_request_with_credentials_included
cookieなどの認証情報をリクエストに付与するにはfetchのoptionのcredentialsをincludeに指定する必要がある。
const url = 'http://127.0.0.1:8000/api/hoge';
try {
const response = await fetch(url, {
credentials: 'include',
});
const json = await response.json();
console.log(json);
} catch (error: any) {
console.warn(error);
}
結果 :
Laravelのログを見るとリクエストを受け付けてレスポンスを返していることが分かった。しかしブラウザのNetworkタブを見るとcorsエラーが発生しているのが分かる。
↓エラーログ
Access to fetch at 'http://127.0.0.1:8000/api/hoge' from origin 'http://localhost:3000' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
↓翻訳
オリジン「http://localhost:3000」から「http://127.0.0.1:8000/api/hoge」でのフェッチへのアクセスは、CORSポリシーによってブロックされました。リクエストの資格モードが「include」の場合、レスポンスの「Access-Control-Allow-Origin」ヘッダーの値は、ワイルドカードの「*」であってはなりません。
エラーログにも書かれているが、リクエストの資格モードが「include」の場合、レスポンスの「Access-Control-Allow-Origin」ヘッダーの値は、ワイルドカードの「」にしてはいけないとのこと。
もう一度レスポンスのヘッダーを見るとAccess-Control-Allow-Originがになっているのが分かる。
Laravelのcors設定
Laravel8でアプリケーションを作成した場合、デフォルトで↓のcorsパッケージが使用される
https://github.com/fruitcake/laravel-cors
cors設定は以下の通り。デフォルトで全てのmethod, origin, headerが許可されている。
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];
allowed_originsを ['http://localhost:3000'] に変更する
再度reactからapiリクエストを投げる
結果はエラー。
オリジン「http://localhost:3000」から「http://127.0.0.1:8000/api/hoge」でのフェッチへのアクセスは、CORSポリシーによってブロックされています。レスポンスの 'Access-Control-Allow-Credentials' ヘッダーの値は '' で、リクエストの資格情報モードが 'include' の場合は 'true' でなければなりません。
Access-Control-Allow-Credentialsがtrueじゃないとだめとのことです。
レスポンスを見るとAccess-Control-Allow-Credentialsのヘッダーはありませんでした。
レスポンスヘッダーにAccess-Control-Allow-Credentialsを返すようにする
cors.phpのsupports_credentialsをtrueに変更する
'supports_credentials' => true,
再度reactからapiリクエストを投げる
リクエストは成功しSet-Cookieに値があるが、何度リクエストを投げてもリクエストヘッダーにCookieは付与されなかった。
chrome開発ツールのcookieタブを見ると、cookieがブロックされていることが分かる。
このSet-Cookieは、"SameSite=Lax "属性を持っていましたが、トップレベルのナビゲーションに対するレスポンスではないクロスサイトレスポンスから来ていたため、ブロックされました。
SameSite Cookieとは
Set-CookieにSameSite属性をつけることでCookieを制限することが可能。
↓ドキュメント
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie/SameSite
3つの値から指定できる。
Lax
デフォルトでcookieはこの値になる。トップレベルナビゲーションで送信可能。
トップレベルナビゲーションとは、タグによる遷移時やformによるget actionなどを指すらしい。
↓ここに書いてある図が分かりやすい。
https://blog.jxck.io/entries/2018-10-26/same-site-cookie.html
Strict
ファーストパーティのコンテキストのみでCookieを送信する。
ファーストパーティクッキーとはブラウザのアドレスに表示されているドメインと同じドメインから発行されたcookieのこと。
None
全てのコンテキストでcookieを送信する。
以前はNoneがデフォルトだったが、最近のブラウザはLaxがデフォルト。これによりcorsの基本的な対策ができるようになった。
cookieをNoneにする場合はcookieにsecure属性が必須となる。
secure属性
secure属性をつけることでhttps通信時のみcookieを送信するようになる
クロスオリジンでapi通信するには?
クロスオリジンでapi投げる場合はSameSite属性が重要になる。
StrictやLaxだとAPIを投げてもcookieがリクエストに付与されないしSet-Cookieされた値もブラウザでブロックされる。
残るはNoneだが、cookieにsecure属性が必要になる。secure属性をつけるとhttps通信の時しかcookieのやりとりができなくなる。
ローカル環境もhttpsで通信する
ローカル開発環境 + クロスオリジンでapi通信する場合は、SameSiteをNoneにしsecure属性付けて通信をhttps化する方法がある。
docker開発環境をつかっているならhttps-portalイメージを使うことで簡単にローカル環境もhttps化できる。
version: '3'
services:
https-portal:
image: steveltn/https-portal:1
ports:
- '4443:443'
environment:
DOMAINS: 'localhost -> http://laravel:8000'
STAGE: local
laravel:
build: ./
volumes:
- ./:/app
FROM php:7.4.16-zts-buster
WORKDIR /app
CMD php artisan serve --host 0.0.0.0 --port=8000
この状況でReactから https://localhost:4443/api/hoge
にapiアクセスすると正常にcookieのやりとりができる。
proxy使う
reactのcreate-react-appを使っているならproxyを使ってcors問題を回避することができる。
"proxy": "http://127.0.0.1:8000"