Laravel 7におけるCORS(Cross-Origin Resource Sharing)リクエストの処理

August 14, 2020
執筆者
Michael Okoko
寄稿者
Twilio の寄稿者によって表明された意見は彼ら自身のものです
レビュー担当者

Laravel 7におけるCORS(Cross-Origin Resource Sharing)リクエストの処理

この記事はMichael Okokoこちらで公開した記事(英語)を日本語化したものです。

ウェブブラウザはデフォルトで同一オリジンポリシーを実装しており、スクリプトが異なるドメイン間でHTTPリクエストを行うことを防ぎます。 Cross-Origin Resource Sharing(CORS)は、自オリジンとは異なるオリジンからのリクエストを許可するか、または制限するかについて、ブラウザとサーバーサイドアプリケーション間で同意形成を行うためのメカニズムを提供します。

バージョン7から、Laravelフレームワークは、Middlewareを使用してCORSヘッダーを送信するためのファーストパーティサポートを備えています。

このチュートリアルでは、Laravelを使用したシンプルなVue.jsアプリを作成し、CORSについて学びます。CORS OPTIONSリクエストを処理するためにLaravelが必要とする様々な設定オプションを深く掘り下げ、これらのオプションがアプリケーションにどのような影響を与えるかをご紹介します。

必要なもの

このチュートリアルを完了するためには、以下の項目を満たしている必要があります。

  • Laravelフレームワークに精通していること。
  • Laravelフレームワークのバージョンが7.0以上のプロジェクトが手元にあること。
  • Vue CLIがインストール済であること。

はじめに

Laravel APIとVueプロジェクトの両方を格納する新しいフォルダを作成し、Laravelプロジェクトを作成します。以下のコマンドを実行してください。

$ mkdir laravel-cors && cd laravel-cors
$ laravel new server

作成されたLaravelプロジェクトには、アプリケーションのニーズに合わせて微調整できる汎用的なCORS設定が$APP_FOLDER/config/cors.phpに含まれています。以下のコードは、新しいLaravelアプリケーション用に生成された設定ファイルのサンプルです(コメントは含まれていません)。

<?php

return [
    'paths' => ['api/*'],
    'allowed_methods' => ['*'],
    'allowed_origins' => ['*'],
    'allowed_origins_patterns' => [],
    'allowed_headers' => ['*'],
    'exposed_headers' => [],
    'max_age' => 0,
    'supports_credentials' => false,
];

VueのフロントエンドからAPIのレスポンスヘッダーを確認するには、以下のように`exposed_headers`配列を変更し、ワイルドカード文字を格納させます。

'exposed_headers' => ['*'],

次に、ダミーデータでリクエストに応答するようにAPIルートを設定します。routes/api.phpファイルを開き、その内容を以下のコードブロックで置き換えます。

<?php
 
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::get('/items', function(Request $request) {
   $data = [
       [
           "id" => 7,
           "name" => "na like this",
           "description" => "",
           "created_at" => "2020-07-26T05:53:00.376501Z",
           "updated_at" => "2020-07-26T05:53:00.376501Z"
       ], [
           "id" => 5,
           "name" => "write a book",
           "description" => "hohoho",
           "created_at" => "2020-07-26T05:47:00.908706Z",
           "updated_at" => "2020-07-26T05:53:00.376501Z"
       ]
   ];
   return response()->json($data);
});

Route::get('/items/{id}', function(Request $request) {
   $data = [
       'id' => 1,
       'name' => "Swim across the River Benue",
       'description' => "ho ho ho",
       'created_at' => "2020-07-26T22:31:04.49683Z",
       'updated_at' => "2020-07-26T22:31:04.49683Z"
   ];
   return response()->json($data);
});

Route::post('/items', function(Request $request) {
   $data = [
       'id' => 1,
       'name' => "Swim across the River Benue",
       'description' => "ho ho ho",
       'created_at' => "2020-07-26T22:31:04.49683Z",
       'updated_at' => "2020-07-26T22:31:04.49683Z"
   ];
   return response()->json($data, 201);
});

Vueフロントエンドの設定

laravel-corsフォルダ内でvue create frontendを実行して、Vueプロジェクトを作成してください。プリセットの設定を促されるので、デフォルトのオプションを選択してください。作成されたフロントエンドのフォルダー内で、HelloWorld.vue(パス:src/components/HelloWorld.vue)を開き、内容を以下のコードブロックに置き換えます。

<template>
 <div class="hello">
   <h1>{{ msg }}</h1>
   <p>Open your Browser console to see the headers sent in by Laravel.</p>
 </div>
</template>
<script>
export default {
 name: "HelloWorld",
 props: {
   msg: String,
 },
 mounted() {
   let req = new Request("http://localhost:8000/api/items", {
     method: "get",
   });
   fetch(req).then((response) => {
     for (let pair of response.headers.entries()) {
       console.log(pair[0] + ": " + pair[1]);
     }
   });
 },
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
 margin: 40px 0 0;
}
ul {
 list-style-type: none;
 padding: 0;
}
li {
 display: inline-block;
 margin: 0 10px;
}
a {
 color: #42b983;
}
</style>

上記のコードにより、Laravelアプリケーションで作成したAPIルートである/itemsにHTTPリクエストを行い、受信したすべてのレスポンスヘッダーを記録します。

試しに、ターミナルからサーバーフォルダ内でphp artisan serveを実行して、Laravelアプリケーションを動かしてみましょう。

サーバーを稼働させたら、フロントエンドディレクトリに移動して、yarn serveを実行し、Vueアプリを起動してください。

yarn serveを実行した際に表示されたローカルURLにブラウザでアクセスし、ページコンソールを開いてください。

以下のスクリーンショットのように、コンソールにレスポンスヘッダーが表示されます。

Vue.jsのウェルカムページがブラウザに表示

ルートプレフィックスのCORSを有効にする

pathsオプションでは、CORSヘッダーを送信するルートやリソースのパスを指定します。上記の設定では、「api」というプレフィックスを持つすべてのルートに対してCORSヘッダーが有効になっています。このプレフィックスを削除したり、別のものに変更したりすると(例:‘paths’ => [’endpoints/*’])、APIエンドポイントのCORSが無効になり、以下のようなエラーが発生します。

コンソールエラー情報

パスは、文字列の完全一致(例:/public/img/photo.img) または、ワイルドカード(例:/public/*または/api/*)を使って指定します。さらに、public/**/*を使用して、指定されたフォルダとそのサブフォルダのすべてのファイルに対してCORSを有効にすることができます。

許可されたHTTPメソッドを検索する

allowed_methodsは、リソースにアクセスする際に許可されるHTTPリクエストメソッドを指定します。ここで追加したメソッドは、クライアントがLaravelアプリケーションにPreflight request(プリフライトリクエスト)を行う際に、“Allowed-Methods”ヘッダーで返されます。Laravelはデフォルトで*の値を使用してすべてのHTTPメソッドに対してCORSを有効にします。

たとえば、allowed_methodsに含まれるワイルドカード*POSTに置き換えると、サーバーにGETリクエストを送信しているためフロントエンドのコードが壊れてしまいます。

許可されたホストを制限する

allowed_origins は、リソースへのアクセスを許可する「origins」を指定します(ここでいう「origins」とは、URLのスキーム、ドメイン、ポートの組み合わせを指します)。上記のオプションと同様に、ワイルドカードによるマッチングも可能です(例:*example.com は、example.comおよびそのいずれのサブドメインに対しリソースへのアクセスを許可します)。デフォルトでは、すべてのオリジンを許可するように設定されています。

: ワイルドカードを使用しない場合は、オリジンを全文で指定する必要があります(例:http://example.comは有効、example.comは無効)

正規表現を使って許可されたホストを制限する

allowed_origins_patternsオプションを使うと、正規表現を使って許可されるオリジンを指定することができます。 allowed_originsでサポートされているワイルドカード*よりも複雑なマッチパターンが必要な場合に便利です。ここで指定する値は、有効なpreg_match()パターンでなければなりません。また、誤って正規表現が包括的になってしまわないように注意してください。たとえば、/https?:\/\/example\.com/https://example.comhttps://example.com.hackersdomain.comにもマッチします。より良いパターンは、/https?:\/\/example\.com\/?\z/で、example.comで終わるドメイン(最後にスラッシュがある場合も含む)に限定されます。

許可されたヘッダーを設定する

allowed_headersオプションは、実際のCORSリクエストで許可されるHTTPヘッダーを定義します。この定義により設定されるAccess-Control-Allow-Headersは、Access-Control-Request-Headersを含むプリフライトリクエストに対する応答として送信されるsヘッダーです。デフォルトの設定ではすべてのHTTPヘッダーを許可するようになっています。

カスタムヘッダーを公開する

Excposed_headersを使用すると、CORSによってデフォルトでsafe-listed(セーフリスト)されていないカスタムHTTPヘッダーに、APIクライアントがアクセスできるようになります。たとえば、LaravelのThrottle middlewareによって設定されるX-RateLimit-RemainingX-RateLimit-Limitヘッダーを公開し、与えられた時間内にあと何回クライアントがリクエストを実行できるかを示したい時などに使用できます。

CORSでセーフリストに登録されているHTTPヘッダーは7つしかないので(Cache-Control、Content-Language、Content-Length、Content-Type、Expires、Last-Modified、Pragma)、アプリケーションが公開しなければならないその他のヘッダーは、exposed_headersに含める必要があります。

CORSレスポンスのキャッシュ

max_ageオプションの値は、クライアントがプリフライトリクエストのレスポンスをキャッシュできる期間(秒)を指定します。Laravelではデフォルトで0に設定されています。ブラウザ指定の最大期間よりも長い場合、max_ageは無視されます(Firefoxでは24時間、Chromium v76以上では2時間)。

CORSによるHTTPセッション

supports_credentialsを使用すると、Access-Control-Allow-Credentialsを設定し、CORSリクエストでCookieやCookieに依存するセッションの送信を許可できます。ただし、すべてのオリジンが許可されている場合(つまり、allowed_origins がワイルドカード(*)文字の場合)、Access-Control-Allow-Credentialsの設定はtrueにはなりません。

まとめ

CORSの設定は時に面倒なものです。Laravelでは、laravel-corsパッケージを利用することで、即時使用可能な設定を簡単に行うことができます。

CORSに関する一般的な知識を習得したい場合は、以下を参照してみてはいかがでしょうか。

Michael Okokoは、ナイジェリアのObafemi Awolowo大学に通うソフトウェアエンジニア兼コンピュータサイエンスの学生です。彼はオープンソースが大好きで、主にLinux、Golang、PHP、そしてファンタジー小説に興味があります。連絡先は以下の通りです: