CORS を設定して別サイトのセッションを取得する


Web アプリケーションを開発していて、別ドメインのログイン状態を取得する際に CORS の仕組みについて勉強することができたので、設定ポイントをまとめておきます。

CORS とは

Cross-Origin Resource Sharing の頭文字をとった略語です。
通常、ブラウザでは異なるサイトへのリソースへのアクセスに制限をかけて、CSRF などの攻撃を防いでいます。 CORS とは、この制限に対し、適切な設定を行うことで異なるサイトのリソースを共有できるようにするものです。

オリジンとは

ブラウザは、同じサイトか異なるサイトかについて、オリジン (Origin) という単位で区別します。
同一スキーム、同一ホストおよび同一ポートのものが同一オリジン (Same-Origin) となります。
同じホストでも、スキーム (http/https) やポートが違っていれば異なるオリジンとなります。

以下は https://www.example.com までが同じなので、同じオリジンとなります。

https://www.example.com/foo/
https://www.example.com/bar/

同じホスト名であっても以下はスキーム (http/https) が違うので、これらは異なるオリジンです。

http://www.example.com/foo/
https://www.example.com/foo/

同じオリジンであれば、とくに制限なくリソースの共有ができます。
異なるオリジンの場合は、CORS の適切な設定が必要となります。

やりたいこと

ブラウザでは事前にサイト A にログイン。サイト A のセッションクッキーを持ちます。
サイト B のページにおいて、Ajax によりサイト A のログイン状態を取得し、
ログインしていればログインユーザー名などを表示します。

それぞれのオリジンを以下とします。

サイト名 URL
サイト A https://site-a.example.com/
サイト B https://site-b.example.com/

HTTP ヘッダーの設定

Access-Control-Allow-Origin

サイト A 側では、サイト B からのアクセスを許可するために、以下のヘッダーを出力するようにします。

Access-Control-Allow-Origin: https://site-b.example.com/

画像ファイルなどの通常のリソースは、この Access-Control-Allow-Origin ヘッダーによる Origin の許可ができていれば共有できます。

Access-Control-Allow-Credential

しかしながら、サイト間の Ajax呼び出しで HTTPクッキーや HTTP認証といった 資格情報 は、デフォルトでは送信されません。
資格情報の取得を許可するには、 以下のように Access-Control-Allow-Credential ヘッダーを入れる必要があります。

Access-Control-Allow-Credential: true

それから、資格情報の場合は、Access-Control-Allow-Origin: では * (アスタリスク)によるワイルドカードは使用できません。明示的に Origin を指定する必要があります。

具体的な設定だと、
Apache なら、

httpd.conf
Header set Access-Control-Allow-Origin https://site-b.example.com/
Header set Access-Control-Allow-Credentials true

nginx なら、こんな感じです。

nginx.conf
add_header Access-Control-Allow-Origin "https://site-b.example.com/";
add_header Access-Control-Allow-Credentials true;

セッションクッキーの設定

サイト A 側で発行するセッションクッキーの設定にも気を配る必要があります。
セキュアクッキーであること、 SameSite が None であることが必要です。

PHPの場合は、 session_set_cookie_params でセッションクッキーの設定を行ってから session_start します。
最近の PHP(7.3.0以上)であれば、SameSite の指定がオプションパラメータの配列で指定できます。

    session_set_cookie_params([
        'lifetime' => $maxlifetime,
        'domain' => $_SERVER['HTTP_HOST'],
        'path' => '/',
        'httponly' => $httponly,
        'secure' => true, // セキュアクッキーであること
        'samesite' => 'None' // SameSite が Noneであること
    ]);
    session_start();

7.3.0 未満の古い PHP の場合、ドメイン・パスの後ろにセミコロンで区切って、SameSite の指定を直接入れてやります。

    session_set_cookie_params($maxlifetime, '/; SameSite=None', '', true);
    session_start();

Ajaxの呼び出し

サイト B からサイト A の Ajax呼び出しを行うときにも withCredentials の設定が必要です。

サイト B の JavaScript

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://site-a.example.com/login_user', true);
xhr.withCredentials = true;
xhr.send(null);

jQuery ならこんな感じ。

$.ajax({
  url: "https://site-a.example.com/login_user",
  xhrFields: {
    withCredentials: true
  }
}).success(function(res){
  // 処理
})

仕組みが理解できても、設定ポイントがいくつかあるので、漏れのないように設定するのがたいへんです。
本記事を参考にチェックしてみてください。

参考