【シンプルリクエスト編】クロスドメイン関連の挙動をJavaScriptで確認する

はじめに

クロスオリジンとはなんぞや、Referrerヘッダの値を操作するにはどうすりゃいいのか、みたいな課題があった。(in PrAha Challenge

とりあえず理論上はクロスオリジンのことは理解したつもりだけど、これは前と同じだと思った。というのもすっかり忘れていたんですが、前にもクロスオリジンに関しては読んだことがあり、当時は理解したけど実装経験がないまま月日が経った今日、すっかり忘れていたのでした。。😥
それに気づいただけマシだと思うけども。。

で、今回も調べるだけだとまた間違いなく忘れるので、今度こそ実装してみることにしました。

スキルの問題で時間かかったけど、確実に頭には残ったのでコードとか経緯とか色々残します。

CORSって?

その前に、やっぱりクロスオリジン(CORS)って何っていうのも忘れそうなので簡単に。

Cross Origin Resource Sharingの略。

以下、MDN Web Docsから引用。

オリジン間リソース共有Cross-Origin Resource Sharing (CORS) は、追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。ウェブアプリケーションは、自分とは異なるオリジン (ドメイン、プロトコル、ポート番号) にあるリソースをリクエストするとき、オリジン間 HTTP リクエストを実行します。
オリジン間リクエストの一例: https://domain-a.com で提供されているウェブアプリケーションのフロントエンド JavaScript コードが XMLHttpRequest を使用して https://domain-b.com/data.json へリクエストを行う場合。

とりあえず、太字のところさえ頭に残しておけばなんとかなりそうかな。

【前提整理】実際にCORSでクロスドメインアクセス

実務経験あって文章読んだだけでわかるのであれば、実装しようとは思わなかったですが、残念ながらないのでやります。

とりあえず以下の記事を参考にしながら何をどう書いていけばいいのか、確認します。

本当はこれじっくり読めばわざわざ実装しなくてもいいんじゃないと思ったのですが、Scalatraなるフレームワークで書かれていて、なんだか見慣れなかったです。。
個人的にはJavaScriptのが見たかったです。今後使いそうだし色々な理由から。

最低限確認したいのは、以下の3つ。
クラスメソッド さんブログでは、プリフライトリクエストとかの確認もしてたけど、ちょっとそこは省略。。

1. CORS許可していないAPI叩くとどうなるのか→CORS許可してないよってことでリクエスト失敗するはず
2. CORS許可してるAPIを叩くとどうなるのか→リクエスト成功して、レスポンス返るはず
3. Referrer-Policyをno-referrerに変更するとどうなるのか→Referrerヘッダがリクエストに含まれないようになるはず

これを別オリジン = 今回の場合はローカルサーバを別ポートで2つ起動して行います。
具体的にはこんな感じ。

クライアントのホスト:http://localhost:8000
サーバーのホスト:http://localhost:3000

ポート番号違ってたら別オリジンって言えるのだから、これで良さそうよね。

【サーバー側実装】実際にCORSでクロスドメインアクセス

さて参考にしたかったクラスメソッド さんブログはScalatraなるものを使ってて(→Java??)、なんの言語が私にはわかりませんでしたが、とにかくどう見てもJavaScriptではなさそうです。

とりあえずAPIを作ればいいみたいなので、色んなところからコピペしてきて作ります。
node.js と expressで作ります。←API関連はこの組み合わせが個人的に1番見慣れてるので。。

POSTは作らず、GETのみ作りました。そしてCORS許可してるAPIと許可してないAPIの2つ作りました。

コードはこんなになった。
corsという便利モジュールあるみたいですが、一旦使わず。

// ライブラリの読み込み
const express    = require("express")
const app        = express()
const bodyParser = require("body-parser")

// body-parserの設定
app.use(bodyParser.urlencoded({ extended: true}))
app.use(bodyParser.json())

// 3000ポートにする
const port = process.env.PORT || 3000

// CORSを許可していないGETのAPI
app.get("/api/v1/ng_cors", function(req, res) {
 res.json({
   message: "CORS許可してないよ"
 })

 console.log("req.headers.origin: " + req.headers.origin)
})

// CORSを許可しているGETのAPI
app.get("/api/v1/ok_cors", function(req, res) {
 // CORSを許可する(corsモジュールを使用するとより簡潔な記載になる)
 // 追記:実際はワイルドーカードを指定するのは危険なので、具体的なhttp://localhost:8000とかを指定した方が良いです
 res.header('Access-Control-Allow-Origin', "*");
 // 追記:今回のシンプルリクエストでは、以下3つのヘッダ設定は不要です(コピペしてきたら無駄コードが残ってしまった、、)
 res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept');
 res.header('Access-Control-Allow-Methods', 'PUT, DELETE, OPTIONS');
 res.header('Access-Control-Allow-Credentials', true);

 res.json({
   message: "CORS許可してるよ"
 })

 console.log("req.headers.origin: " + req.headers.origin)
})

// サーバ起動
app.listen(port)
console.log("listen on port: " + port)

できたっぽいので、node app/app.jsして3000番ポートでローカルサーバ起動してとりあえずPOSTMANでリクエストしてみる。
結果、「CORS許可してないよ」と「CORS許可してるよ」と返ってきたので、とりあえず同ドメイン間で2API共問題なさそうなことを確認できた。

次クライアントやる。

【クライアント側実装】実際にCORSでクロスドメインアクセス

クライアントは、ちっちゃなページにボタンを2種類用意して、
1つはCORS許可してないAPIを、もう1つは許可してるAPIを叩くようにJavaScript書く。

ボタン押した時に何が起きてるかを、ChromeのDeveloper Toolで見る。そしたら目的達成できそう。

コードはこんな。またいろんなところの記事からコピペしてくる。
javascriptのファイル分けろよと思わなくもないが、まあテストなのでご了承。。

<!DOCTYPE html>
<html lang="ja">
 <head>
   <meta charset="UTF-8"/>
   <title>Document</title>
 </head>
 <body>
   <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>

   <button id="ng_cors">CORS無許可確認ボタン</button>
   <button id="ok_cors">CORS許可確認ボタン</button>

   <script type="text/javascript">
     $(function () {
       // CORS無許可確認ボタンクリック時
       $("#ng_cors").on("click", function () {
         $.ajax({
           type: "GET",
           url: "http://localhost:3000/api/v1/ng_cors",
         })
           // Ajaxリクエストが成功した時発動
           .done((data) => {
             console.log(data);
           })
           // Ajaxリクエストが失敗した時発動
           .fail((data) => {
             console.log(data);
           })
           // Ajaxリクエストが成功・失敗どちらでも発動
           .always((data) => {});
       });

       // CORS許可確認ボタンクリック時
       $("#ok_cors").on("click", function () {
         $.ajax({
           type: "GET",
           url: "http://localhost:3000/api/v1/ok_cors",
         })
           // Ajaxリクエストが成功した時発動
           .done((data) => {
             console.log(data);
           })
           // Ajaxリクエストが失敗した時発動
           .fail((data) => {
             console.log(data);
           })
           // Ajaxリクエストが成功・失敗どちらでも発動
           .always((data) => {});
       });
     });
   </script>
 </body>
</html>

【結果確認】実際にCORSでクロスドメインアクセス

コードはできたので、確認すべく色々準備する。

1. ローカルサーバー2つを別ポートで起動する
サーバー側:node app/app.jsで起動する。3000ポートになってるはず。
クライアント側:htmlを置いてるディレクトリで、python3 -m http.server 8000で起動する。8000ポートになってるはず。

2. http://localhost:8000 にブラウザでアクセスする
3. ブラウザでDeveloper Tool開く

ではまず、「1. CORS許可していないAPI叩くとどうなるのか」の確認。
期待結果はこれ→「CORS許可してないよってことでリクエスト失敗するはず」

「Access to XMLHttpRequest at 'http://localhost:3000/api/v1/ng_cors' from origin 'http://localhost:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.」だって。CORS許可してないよってことなので想定通りですね。わーい。

画像1

次、「2. CORS許可してるAPIを叩くとどうなるのか」の確認。
期待結果はこれ→「リクエスト成功して、レスポンス返るはず」

うん。ちゃんと「CORS許可してるよ」が返ってきた。

画像2

最後、「3. Referrer-Policyをno-referrerに変更するとどうなるのか」の確認。
期待結果はこれ→「Referrerヘッダがリクエストに含まれないようになるはず」

その前にまず、今ののReferrer-Policyの値を確認すると、「strict-origin-when-cross-origin」だそう。Referrerヘッダも存在してました。

では早速、no-referrerに変えていきます。
Referrer-PolicyヘッダをクライアントのJavaScriptで指定するのかなと思ったのですが、なんか良い感じのが見つからなかったので、<meta>タグで指定することにしました。

クライアントコードの4行目を以下に書き換えます。

<meta charset="UTF-8" name="referrer" content="no-referrer" />

ちなみにReferrer制御はこちらの記事を参考にしましたよ。

その上で、「CORS許可確認ボタン」を押してみます。
Referrer-Policyが「no-referrer」になりましたね!ヘッダから「Referrer」もなくなっています。めでたし。

画像3

最後に

だいぶ満足しました。正直、CORS許可するためにたくさん書いたヘッダ名たちはまだ全部理解してないんですが、
やりたいことはできたし、クロスドメインがどういう場面で必要になりそうかもなんとなくイメージできました。

プリフライトリクエストを確認できなかったのが心残りですが、そろそろ力尽きてきたので、それはまた別の機会にします。😪

終わり。


この記事が気に入ったらサポートをしてみませんか?