クロスドメインなセッション

やりたいこと

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',
});

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 に書かれている。

まとめ