tohokuaikiのチラシの裏

技術的ネタとか。

今更ながらORIGINの違う(≒クロスドメイン)のAPIをブラウザからのJavaScriptで叩くときにCORSとかいろいろとありがとうございました。

JavaScriptドメインを越えるにはCORS設定をしなければならない。ドメインというか、正確にはORIGINだけど。

ORIGINってこの3つから成り立ってる。

項目
スキーム http / https
ホスト名 example.com / localhost
ポート 80, 443, 3000, etc.

以下、ブラウザが表示しているWEBページを http://localhost:8080 (送信元ORIGIN)として、そのWEBページからXHRで叩きたいAPIhttp://localhost:8000API ORIGIN)とする。

API側は、CORSヘッダを適切にブラウザに返さなければならない。

送ってきたリクエストに対してCORS(Cross-Origin Resource Sharing)が成立するためには以下のヘッダを適切に送らなければならない。適切にというのは、イケナイ送信元ORIGINだったらこれらを送らないということです。サンプルはPHPのSlim Frameworkだが、まあだいたいわかるだろう。

<?php
// $originは(送信元ORIGIN)であること。
$response =  $response->withHeader('Access-Control-Allow-Origin', $origin)
                        ->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
                        ->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
return $response;

CORSエラーの場合にJavaScriptではどうするか。

axiosを使っている場合だが、CORS エラーが発生した場合、サーバーからのレスポンスがJavaScriptまで届かないようにブラウザがブロックしてしまう。なのでこんな感じでCORSエラーを処理する。

try {
    await axios.post(url, data);
} catch (e) {
    if (axios.isAxiosError(e)) {
        if (e.response === undefined && e.request !== undefined){
             alert('ネットワークエラー');
        }
        setErrors(e.response?.data.errors);
    }
}

COOKIEを使いたい場合

更にCOOOKIEについてもCORSが成立するために、はヘッダーが必要である。これはクライアントとサーバーの両方で必要になる。

クライアント側(JavaScript

// fetch
fetch('https://api.example.com/endpoint', {
  method: 'POST',
  credentials: 'include', // ← これが必須
});

// axios
axios.post(url.confirm, data, {
   withCredentials: true
});

サーバ側(PHPのSlim Framework)

<?php
$response =   $response->withHeader('Access-Control-Allow-Origin', $origin)
                        ->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization')
                        ->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
                        ->withHeader('Access-Control-Allow-Credentials', 'true'); // trueではなく文字列の"true"

すげぇ面倒です。

サーバ側(追加)

POSTの前にはPreflightがOPTIONメソッドで飛ぶので、全てのPOSTルーティングに対してOPTIONルートも同様のCORS対応ヘッダを送るようにしないといけないよ。