概要
LaravelでAPIを作成している際に、CORSの設定を行いました。その際にCORSについて色々と調べたので、その内容をわかりやすくまとめたいと思いこの記事を書いています。
初心者の方にも理解できることを目標に書いたので、ご参考になれば幸いです。
CORSとは
ブラウザは、セキュリティを高めるために同一オリジンポリシーが適用されています。この同一オリジンポリシーは、あるオリジンから他のオリジンのリソースにアクセスできないように制限するものです。
このポリシーが適用されているため、webアプリケーションから異なるドメインにアクセスする(外部のAPIを利用するなど)場合には、エラーとなってしまいます。
このエラーを回避し、クロスドメインアクセスを可能にするための仕組みとして用意されているのが、CORSです。
クロスドメインアクセスをした場合、実際にどんなエラーとなるのか確認してみましょう。
webアプリケーションがオリジンの異なるAPIを利用するケースです。
webアプリケーションhttp://localhost:8080
から
サーバー(API)http://127.0.0.1:8000/api/accounts
に対してリクエストすると、以下のエラーが表示されます。
Failed to load http://127.0.0.1:8000/api/accounts:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:8080' is therefore not allowed access.
The response had HTTP status code 500.
簡単に日本語に訳してみると、
「Access-Control-Allow-Origin」ヘッダーがないから、Originhttp://localhost:8080
は
http://127.0.0.1:8000/api/accounts
にアクセスできないよ!という内容です。
ここで出てきたAccess-Control-Allow-Origin
ヘッダーをレスポンスに追加することが異なるオリジン間でのアクセスを可能にする仕組みになります。
また、エラーメッセージで指定されているOriginは、Request HeadersのOriginヘッダーで設定されています。
異なるオリジンへのリクエストの場合に設定されます。
では、実際にCORSの設定をしていきたいのですが、その前にPreflightリクエストについて見ていきたいと思います。
Preflight(プリフライト)リクエスト
ここでは、あるwebアプリケーションから異なるオリジンのリソースにアクセスすることを「オリジン間リソース共有」と呼ぶことにします。
オリジン間のリソース共有において、サーバーの情報に副作用を引き起こす可能性があるリクエストには、「Preflightリクエスト」と呼ばれるリクエストを事前に送信する仕組みがあります。Preflightリクエストを送信し、サーバー側が問題ないと判断した場合に、実際のリクエストの送信が可能となります。
以下の条件に一つでも当てはまらない場合は、Preflightリクエストが送信されます。全て当てはまっている場合は、実際のリクエストのみが送信されます。
- HTTPメソッドが
GET
、HEAD
、POST
のいずれかである - HTTPヘッダーに以下のもの以外が含まれない
- Accept
- Accept-Language
- Content-Language
- Content-Type (例外あり)
- Content-Typeが以下のいずれかである
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
では、Preflightリクエストが送信される例を見てみましょう。
webアプリケーションからサーバ(API)に対して、JSON形式でPOSTします。
(Content-Type
ヘッダーにapplication/json
を指定して、POSTする例です。)
Chromeのデベロッパーツールを確認すると、リクエストメソッドがOPTIONS
になっていますね。
これが、実際のリクエストの前に送信されるPreflightリクエストです。
Preflightリクエストが必要かどうかは、ブラウザが判断しています。
また、Request Headersの中身は以下のようになっています。
いくつかのフィールドが指定されているので、それぞれについて見ていきましょう。
Origin
クライアントのオリジンがセットされています。このOriginヘッダーが存在すると、CORSのリクエストであると判断されます。
Access-Control-Request-Method
実際のリクエストのHTTPメソッドが指定されています。今回はPOST
が指定されています。
Access-Control-Request-Headers
実際のリクエストのHTTPヘッダーが指定されています。今回はContent-Type
が指定されています。
Preflightリクエストが送信された場合、サーバーサイドでCORSの設定をしていないと以下のエラーが表示されます。
Failed to load http://127.0.0.1:8000/api/accounts:
Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access.
Access-Control-Allow-Origin
ヘッダーがないと怒られていますね。
ここまでで、オリジン間のリソース共有するためには、CORSの設定をしないとエラーとなってしまうことがわかりました。
では、次にLaravelのミドルウェアを作成しながら、CORSの設定について見ていきましょう。
CORSを設定するミドルウェアを作成
ミドルウェアを作成する
以下のコマンドを実行して、ミドルウェアを作成します。
$ php artisan make:middleware Cors
作成されたミドルウェアを以下のように修正します。
<?php
namespace App\Http\Middleware;
use Closure;
class Cors
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
return $next($request)
->header('Access-Control-Allow-Origin', 'http://localhost:8080')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
->header('Access-Control-Allow-Headers', 'Content-Type');
}
}
このミドルウェアでは、レスポンスのHTTPヘッダーにフィールドを3つ追加しています。それぞれの役割を見て見ましょう。
Access-Control-Allow-Origin
サーバー(API)へのアクセスを許可するオリジンを指定します。
上記の例だと、http://localhost:8080
からのアクセスは許可するけれど、それ以外はアクセス不可という設定になります。
よくサンプルにある'Access-Control-Allow-Origin', '*'
は、全てのドメインからのアクセスを許可しますという意味になります。
Preflightリクエストが送信されない場合、このヘッダーのみを追加すればオリジン間のリソース共有が可能となります。
Access-Control-Allow-Methods
許可するHTTPリクエストのメソッドを指定します。
PreflightリクエストのAccess-Control-Request-Method
で指定される値が設定されることになります。
Access-Control-Allow-Headers
許可するHTTPヘッダーを指定します。
PreflightリクエストのAccess-Control-Request-Headers
で指定される値が設定されることになります。
上記の例では、Content-Type
ヘッダーが指定されてリクエストされることを想定しているため、Content-Type
が指定されています。
ヘッダーを複数許可する場合は、カンマ区切りで指定します。
header('Access-Control-Allow-Headers', 'Content-Type, X-Requested-With');
カーネルに追加する
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'cors' => \App\Http\Middleware\Cors::class, // 追加
];
routeを修正
Route::middleware(['cors'])->group(function () {
Route::options('accounts', function () {
return response()->json();
});
Route::post('accounts', 'AccountController@create');
});
Preflightリクエストの場合を考慮して、Routeにoptions
を追加しています。
以上で、CORSの設定は完了し、クロスドメインアクセスが可能となりました。
最後に
今回は、CORSを理解するために自前でミドルウェアを作成しましたが、barryvdh/laravel-cors を利用することも可能です。こちらを利用すると、細かい設定などが可能となるようです。
また、Cookieを許可するケースについては触れていませんので、参考に載せたMDNを確認していただきたいと思います。