クロスオリジンでcookieの挙動を確認する


ローカル環境で2オリジンの環境を用意してcookieの挙動を調べる。自分用メモ

## 環境構築

1つ目の環境はreact create-react-appで作成し、yarn startでサーバーを立ち上げる

```
$ npx create-react-app react_test --template typescript
$ yarn start
```

http://localhost:3000/

```App.tsx
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;

```

![スクリーンショット 2021-03-13 21.36.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278474/439a46d7-da7a-2538-7a69-02e95c5c6990.png)


2つ目の環境はlaravelで作成し、php artisan serveでサーバーを立ち上げる

```
$ composer create-project laravel/laravel laravel_test
$ php artisan sever
```

http://127.0.0.1:80/

```api.php
# cookieにpiyoをつける
Route::get('/hoge', function (Request $request) {
    $piyo = $request->cookie('piyo');
    return response()->json(['piyo' => $piyo])->cookie('piyo', 'pppp', 100000);
});

```

## シンプルにapi投げる
fetchのオプションなしでシンプルにapi投げる。

```App.tsx
    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);
    }
```

### 結果
リクエスト成功しcookieも受け取っているように見える。
![スクリーンショット 2021-03-13 23.36.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278474/bd9059eb-224e-6a29-6299-dcd070e3283b.png)

![スクリーンショット 2021-03-13 23.38.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278474/fea6fee1-f919-028e-a28a-da28f2a0cc4e.png)

ただし、２回目apiなげてもcookieはセットされていない。
![スクリーンショット 2021-03-13 23.39.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278474/0cbbf508-a293-b662-ae59-ab0181184e56.png)


## 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に指定する必要がある。

```App.tsx
    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エラーが発生しているのが分かる。

![スクリーンショット 2021-03-13 23.43.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278474/ef238867-6143-9754-f74d-d120063f73be.png)

![スクリーンショット 2021-03-13 23.44.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278474/53b97ff6-55b3-bd09-988a-d6ddbc143772.png)
![スクリーンショット 2021-03-13 23.46.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278474/9f672678-1e06-f0d8-3958-a19d4a1eaea9.png)

↓エラーログ
> 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が*になっているのが分かる。
![スクリーンショット 2021-03-13 23.49.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278474/2af4828b-73ee-3fb9-dae6-426a35ecbded.png)


### Laravelのcors設定
Laravel8でアプリケーションを作成した場合、デフォルトで↓のcorsパッケージが使用される
https://github.com/fruitcake/laravel-cors
cors設定は以下の通り。デフォルトで全てのmethod, origin, headerが許可されている。

```cors.php
<?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' でなければなりません。
![スクリーンショット 2021-03-14 0.00.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278474/f0de18ea-611e-83fa-bb12-37a6c380bae6.png)

Access-Control-Allow-Credentialsがtrueじゃないとだめとのことです。
レスポンスを見るとAccess-Control-Allow-Credentialsのヘッダーはありませんでした。

![スクリーンショット 2021-03-14 0.01.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278474/ab19c6f3-bfcf-0278-e5da-b050fcd8d3f8.png)

### レスポンスヘッダーにAccess-Control-Allow-Credentialsを返すようにする
cors.phpのsupports_credentialsをtrueに変更する

```cors.php
'supports_credentials' => true,
```

### 再度reactからapiリクエストを投げる
リクエストは成功しSet-Cookieに値があるが、何度リクエストを投げてもリクエストヘッダーにCookieは付与されなかった。
![スクリーンショット 2021-03-14 0.04.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278474/346043d4-6315-0732-4e26-49d8efaa4b77.png)

chrome開発ツールのcookieタブを見ると、cookieがブロックされていることが分かる。
> このSet-Cookieは、"SameSite=Lax "属性を持っていましたが、トップレベルのナビゲーションに対するレスポンスではないクロスサイトレスポンスから来ていたため、ブロックされました。
![スクリーンショット 2021-03-14 0.06.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278474/fbd8c8e9-7f0e-69c6-8a81-afab81180cdc.png)

## SameSite Cookieとは
Set-CookieにSameSite属性をつけることでCookieを制限することが可能。
↓ドキュメント
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie/SameSite

3つの値から指定できる。

### Lax
デフォルトでcookieはこの値になる。トップレベルナビゲーションで送信可能。
トップレベルナビゲーションとは、<a>タグによる遷移時や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化できる。

```docker-compose.yaml
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
```

```Dockerfile
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のやりとりができる。
![スクリーンショット 2021-03-14 2.20.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278474/824e81f7-86e0-0197-0e67-7c29f6e8f23c.png)


### proxy使う
reactのcreate-react-appを使っているならproxyを使ってcors問題を回避することができる。

```package.json
"proxy": "http://127.0.0.1:8000"
```

https://create-react-app.dev/docs/proxying-api-requests-in-development/