その他

【セキュリティー】CORS(Cross-Origin Resource Sharing)まとめ Part 2

引き続きCORSのまとめをやっていきたいと思います。今回は認証情報を含むリクエストに関してです。それではさっそく見ていきます!

withCredentialsプロパティ

デフォルトではクロスオリジンに対するリクエストにHTTP認証やクッキーなどの認証に用いられるリクエストヘッダは自動的に送信されません。これらを用いるためにはXMLHttpRequestのwithCredentialsプロパティをtrueでセットする必要があります。

withCredentialsプロパティをセットしない場合

まずはセットしない場合のサーバーとのやりとりを見ていきます。

GET http://example.jp/33/33-005.html HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://example.jp/33/
Upgrade-Insecure-Requests: 1
Host: example.jp
HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Wed, 05 May 2021 23:34:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 408
Connection: keep-alive
Last-Modified: Mon, 14 May 2018 13:08:18 GMT
ETag: "198-56c2a2decfddb-gzip"
Accept-Ranges: bytes
Vary: Accept-Encoding
X-UA-Compatible: IE=edge

<body>
<script>
  var req = new XMLHttpRequest();
  req.open('GET', 'http://api.example.net/33/33-006.php');
  req.onreadystatechange = function() {
      if (req.readyState == 4 && req.status == 200) {
         var span = document.getElementById('counter');
        span.textContent = req.responseText;
      }
  };
  req.send(null);
</script>
呼び出しカウンター:<span id="counter"></span>
</body>
GET http://api.example.net/33/33-006.php HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: */*
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Origin: http://example.jp
Connection: keep-alive
Referer: http://example.jp/
Host: api.example.net
HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Wed, 05 May 2021 23:34:34 GMT
Content-Type: application/json
Content-Length: 11
Connection: keep-alive
Set-Cookie: PHPSESSID=jrpe1qbb85250e8m6ts9pes4j3; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Access-Control-Allow-Origin: http://example.jp
X-UA-Compatible: IE=edge

{"count":1}

サーバーはSet-Cookieヘッダでクッキーをセットしようとしていることが分かります。ブラウザにクッキーがセットされているか確認するためリロードしてリクエストヘッダーを確認します。

GET http://api.example.net/33/33-006.php HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: */*
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Origin: http://example.jp
Connection: keep-alive
Referer: http://example.jp/
Cache-Control: max-age=0
Host: api.example.net

クッキーがセットされていないことが分かります。

withCredentialsプロパティをセットする場合

今度はwithCredentialsプロパティをセットしてクロスオリジンへリクエストを投げてみます。

var req = new XMLHttpRequest();
req.open('GET', 'http://api.example.net/33/33-006.php');
req.withCredentials = true; # 追記

今度はブラウザのコンソールでエラーが置きました。

クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、http://api.example.net/33/33-006.php にあるリモートリソースの読み込みは拒否されます (理由: CORS ヘッダー ‘Access-Control-Allow-Credentials’ は ‘true’ であるべき)。

withCredentialsプロパティをtrueにしたリクエストでは、サーバーはAccess-Control-Allow-Credentials: trueというヘッダーを返さなければなりません。

最後にレスポンスヘッダーをセットし、正しくクッキーがセットされたやりとりを見ておきます。

GET http://api.example.net/33/33-006b.php HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: */*
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Origin: http://example.jp
Connection: keep-alive
Referer: http://example.jp/
Host: api.example.net
HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Wed, 05 May 2021 23:51:34 GMT
Content-Type: application/json
Content-Length: 11
Connection: keep-alive
Set-Cookie: PHPSESSID=1aaucob4d3qsqtgas69tdnfch2; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Access-Control-Allow-Origin: http://example.jp
Access-Control-Allow-Credentials: true
X-UA-Compatible: IE=edge

{"count":1}

リロードしてみると、クッキーがセットされているのが分かります。

GET http://api.example.net/33/33-006b.php HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:88.0) Gecko/20100101 Firefox/88.0
Accept: */*
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Origin: http://example.jp
Connection: keep-alive
Referer: http://example.jp/
Cookie: PHPSESSID=1aaucob4d3qsqtgas69tdnfch2
Cache-Control: max-age=0
Host: api.example.net

もぐくん
もぐくん
やったね!

  • XMLHttpRequestオブジェクトのwithCredentialsプロパティをtrueにする
  • レスポンスヘッダとしてAccess-Control-Allow-Credentials: trueを返す

さいごに

認証を含むリクエストの場合、プロパティを変更したりヘッダーをセットしたりと色々なことをしないといけないことが分かりました。今後、自分で認証を実装するときはこういうことに気をつけたいと思いました。

ここまで読んでいただきありがとうございました。

ABOUT ME
酒井 駿
名古屋工業大学大学院卒業後、豊田合成(株)で品質管理を経験し、その後スタートアップ・マネーフォワードを経て、2024年11月に株式会社EGGHEAD創業。 製造業とエンジニアリング、両方の現場の知見を活かし、製造業における生成AIを活用した業務改善やシステム開発を支援します。