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

## 概要
LaravelでAPIを作成している際に、CORSの設定を行いました。その際にCORSについて色々と調べたので、その内容をわかりやすくまとめたいと思いこの記事を書いています。

初心者の方にも理解できることを目標に書いたので、ご参考になれば幸いです。

## CORSとは

ブラウザは、セキュリティを高めるために[同一オリジンポリシー](https://developer.mozilla.org/ja/docs/Web/Security/Same-origin_policy)が適用されています。この同一オリジンポリシーは、あるオリジンから他のオリジンのリソースにアクセスできないように制限するものです。
このポリシーが適用されているため、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」ヘッダーがないから、Origin`http://localhost:8080`は
`http://127.0.0.1:8000/api/accounts`にアクセスできないよ！という内容です。

ここで出てきた`Access-Control-Allow-Origin`ヘッダーをレスポンスに追加することが異なるオリジン間でのアクセスを可能にする仕組みになります。

また、エラーメッセージで指定されているOriginは、Request HeadersのOriginヘッダーで設定されています。
異なるオリジンへのリクエストの場合に設定されます。

<img width="902" alt="preflight3.png" src="https://qiita-image-store.s3.amazonaws.com/0/206741/c7120112-531d-9c93-9643-8aea9b83edff.png">


では、実際に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する例です。)

<img width="364" alt="preflight.png" src="https://qiita-image-store.s3.amazonaws.com/0/206741/22b5dc41-417a-e5bb-8d8a-25841e3c54df.png">

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

また、Request Headersの中身は以下のようになっています。
<img width="908" alt="preflight2.png" src="https://qiita-image-store.s3.amazonaws.com/0/206741/7af124ed-3283-b025-770d-c4e32dda7638.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
```

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

```php5: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を修正

```php5: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](https://github.com/barryvdh/laravel-cors) を利用することも可能です。こちらを利用すると、細かい設定などが可能となるようです。

また、Cookieを許可するケースについては触れていませんので、参考に載せたMDNを確認していただきたいと思います。


## 参考
[オリジン間リソース共有 (CORS) - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/HTTP_access_control#Simple_requests)