Help us understand the problem. What is going on with this article?

CORSについて理解してLaravel5.6で対応する

More than 1 year has passed since last update.

概要

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ヘッダーで設定されています。
異なるオリジンへのリクエストの場合に設定されます。

preflight3.png

では、実際にCORSの設定をしていきたいのですが、その前にPreflightリクエストについて見ていきたいと思います。

Preflight(プリフライト)リクエスト

ここでは、あるwebアプリケーションから異なるオリジンのリソースにアクセスすることを「オリジン間リソース共有」と呼ぶことにします。

オリジン間のリソース共有において、サーバーの情報に副作用を引き起こす可能性があるリクエストには、「Preflightリクエスト」と呼ばれるリクエストを事前に送信する仕組みがあります。Preflightリクエストを送信し、サーバー側が問題ないと判断した場合に、実際のリクエストの送信が可能となります。

以下の条件に一つでも当てはまらない場合は、Preflightリクエストが送信されます。全て当てはまっている場合は、実際のリクエストのみが送信されます。

  • HTTPメソッドがGETHEADPOSTのいずれかである
  • 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する例です。)

preflight.png

Chromeのデベロッパーツールを確認すると、リクエストメソッドがOPTIONSになっていますね。
これが、実際のリクエストの前に送信されるPreflightリクエストです。
Preflightリクエストが必要かどうかは、ブラウザが判断しています。

また、Request Headersの中身は以下のようになっています。
preflight2.png

いくつかのフィールドが指定されているので、それぞれについて見ていきましょう。

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

作成されたミドルウェアを以下のように修正します。

Cors.php
<?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');

カーネルに追加する

app/Http/Kernel.php
    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を修正

routes/api.php
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を確認していただきたいと思います。

参考

オリジン間リソース共有 (CORS) - HTTP | MDN

kobayashi-m42
フリーランスのWEBエンジニアです。AWS /Docker /Go /Laravel /Vue.js
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした