hirapi's blog

ちゃんとしたふりをする

XMLHttpRequestでクロスドメインリクエスト

つまり

XMLHttpRequestでクロスドメインのリクエストを送って、リクエスト先でCookie付けたりリクエスト元に値を返したりするときは、XMLHttpRequest・レスポンスヘッダに↓をつけること。

  • XMLHttpRequest
    withCredentials: true
  • レスポンスヘッダ
    • Access-Control-Allow-Origin: (リクエスト元ドメイン)
    • Access-Control-Allow-Credentials: true

(リクエスト元ドメイン)のところに * を使うと、もうひとつの Access-Control-Allow-Credentials がきかずエラーになり、Cookieが付与できない。
サーバー側のプログラムでリファラを取得して動的に指定してあげると通る。
ただしセキュリティ的にそれでいいのか感はある。

くわしく

やりたいこと

  1. XMLHttpRequestで異なるドメインのサーバーにあるプログラムにリクエスト飛ばす
  2. リクエスト先のプログラムでCookieを付与する
  3. リクエスト元に値を返す

fooドメインのjsからbarドメインPHPにリクエスト飛ばして、PHPCookie書き込んで、PHPからjsに値を返したい。

NG

こんな感じで送るとうまくいかない。

リクエスト元

// client.js
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
    if (this.readyState === 4 && this.status === 200) {
        var id = this.response.id;
        console.log(id);
    }
};

xhr.open('GET', 'https://bar.jp/server.php', true);
xhr.send();

リクエスト先

<?php
// server.php

header('HTTP/1.0 200 OK');

$id = 'hogehoge';

setcookie(
    'id',
    $id,
    time() + 60 * 60 * 24 * 180,
    '/',
    'bar.jp'
);

echo json_encode(array('id' => $id));

Chromeだとこういうエラー

XMLHttpRequest cannot load https://bar.jp/server.php. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://foo.jp' is therefore not allowed access. The response had HTTP status code 500.

さらにCookieの付与もできていない。Cookie読むのはできるけど書けない。

OK

  1. リクエスト元のjsで、XMLHttpRequestに↓を設定 withCredentials: true
  2. リクエスト先のサーバーで、レスポンスヘッダに↓を追記
    • Access-Control-Allow-Origin: (リクエスト元ドメイン)
    • Access-Control-Allow-Credentials: true
  3. ※ ↑のリクエスト元ドメインワイルドカード以外を指定する
    • 限定されるならそのドメイン
    • なんらかの事情で限定できないなら、リファラを取得するなりで動的に指定する

サーバー側で付けている Access-Control-Allow-Origin: (リクエスト元ドメイン) はクロスドメインリクエストで値を返したりCookieを付けたりするために必要。
どうでもいい値を返すだけなら「どこからでも良いよ」の意で Access-Control-Allow-Origin: *ワイルドカード指定しても良い。

ただし、ここでワイルドカード指定すると、Cookieなど大事な情報を扱うための Access-Control-Allow-Credentials: true がきかなくなる。
リクエスト元が自分の運用する別ドメインなど限定できるなら、ちゃんと Access-Control-Allow-Origin: https://bar.jp と指定する。
大人の事情で限定できず、事実上「どこからでも良いよ」な状況なのであれば、サーバー側でリファラを取得して指定すると通る。

これをjsとPHPで実装するとこうなる↓

// client.js
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
    if (this.readyState === 4 && this.status === 200) {
        var id = this.response.id;
        console.log(id);
    }
};

xhr.open('GET', 'https://bar.jp/server.php', true);
xhr.withCredentials = true;
xhr.send();
<?php
// server.php

header('HTTP/1.0 200 OK');
header('Access-Control-Allow-Credentials: true');

$parsed_url = parse_url($_SERVER['HTTP_REFERER']);
header('Access-Control-Allow-Origin: ' . $parsed_url['scheme'] . '://' . $parsed_url['host']);


$id = 'hogehoge';

setcookie(
    'id',
    $id,
    time() + 60 * 60 * 24 * 180,
    '/',
    'bar.jp'
);

echo json_encode(['id' => $id]);

実際は「サーバー側で事故ったら500」とかの処理もあったけど今回の話にかかわるところはこんな感じ。

おわり

値を返すところ、JSONPなるものをうまくつかうといいのかもしれない。
今回つかわなかったからまた機会があれば。