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/ |


![cors-session.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/926617/c80e6c44-d41c-e1e3-d2c8-fedaa074f45f.png)


# 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 の指定がオプションパラメータの配列で指定できます。

```php
    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 の指定を直接入れてやります。

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


# Ajaxの呼び出し

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

サイト B の JavaScript

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

jQuery ならこんな感じ。

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

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

# 参考

* https://developer.mozilla.org/ja/docs/Web/HTTP/CORS