やりたいこと
a.localhost から Ajax で b.localhost へリクエストを送り(クロスドメイン)、セッションを確立する。
基本のコード
Ajax でリクエストを送り JSON を受け取る。
a.localhost の index.html
<!DOCTYPE html>
<meta charset="utf-8" />
<p>session 1: <span id="session1"></span></p>
<script>
/**
* @param {{url: string, field: string}} param
*/
async function run(param) {
const res = await fetch(param.url, {
method: 'POST',
});
const json = await res.json();
const field = document.getElementById(param.field);
if (!field) {
throw new Error('表示する要素がないよ');
}
field.textContent = json;
}
(async () => {
await run({
url: 'http://b.localhost/session1.php',
field: 'session1',
});
})();
</script>
b.localhost の session1.php
セッションを生成して適当なキーに適当な値をセットし、その値を JSON として返す。
レスポンスヘッダ Access-Control-Allow-Origin は * ではなく正確なオリジンを指定する。理由は後述する。
<?php
session_start();
$_SESSION['val'] = 123;
header('Access-Control-Allow-Origin: http://a.localhost');
header('Content-type: application/json');
echo json_encode($_SESSION['val']), PHP_EOL;
結果
session 1: 123
と表示される。
Ajax で JSON を受け取ることには成功しているが、セッションが確立しているかどうかはまだわからない。
セッション確認のために修正
リクエストを 2 回送る。最初のリクエストでセッションに値をセットし、2 回目のリクエストでその値を参照する。
a.localhost の index.html
<!DOCTYPE html>
<meta charset="utf-8" />
<p>session 1: <span id="session1"></span></p>
<p>session 2: <span id="session2"></span></p>
<script>
/**
* @param {{url: string, field: string}} param
*/
async function run(param) {
const res = await fetch(param.url, {
method: 'POST',
});
const json = await res.json();
const field = document.getElementById(param.field);
if (!field) {
throw new Error('表示する要素がないよ');
}
field.textContent = json;
}
(async () => {
await run({
url: 'http://b.localhost/session1.php',
field: 'session1',
});
await run({
url: 'http://b.localhost/session2.php',
field: 'session2',
});
})();
</script>
b.localhost の session2.php
session1.php は前と一緒で、session2.php はセッションの値をそのまま返す。
<?php
session_start();
header('Access-Control-Allow-Origin: http://a.localhost');
header('Content-type: application/json');
echo json_encode($_SESSION['val']), PHP_EOL;
結果
session 1: 123
session 2:
と表示される。つまりセッションが確立していない。
どうしてこうなった
リクエストの際に Cookie を送信していないためセッションが途切れている。以下 MDN より引用。
ブラウザーがレスポンスを JavaScript コードに公開するようにするためには、サーバー側 (
Access-Control-Allow-Credentialsヘッダーを使用) とクライアント側 (XHR, Fetch Ajax リクエストの資格情報モードの設定) の両方が、資格情報を含むことを承認しなければなりません。
解決策
リクエストとレスポンスの両方で、Cookie を使ってもいいよと設定する。
a.localhost の index.html
fetch() の部分だけ抜粋。
const res = await fetch(param.url, {
method: 'POST',
mode: 'cors',
credentials: 'include',
});
mode: 'cors'はクロスオリジンのリクエストを許可している。credentials: 'include'はクロスオリジンでも Cookie を送信すると設定している。
b.localhost の session1(2).php
header() の部分だけ抜粋。
header('Access-Control-Allow-Origin: http://a.localhost');
header('Access-Control-Allow-Credentials: true');
header('Content-type: application/json');
資格情報(Cookie とか)を送信する場合、Access-Control-Allow-Origin にオリジンを設定しないとブラウザがお怒りになる、と MDN に書かれている。
まとめ
- 受信側、送信側ともに適切な設定が必要
- というか オリジン間リソース共有 (CORS) をちゃんと読もう