🙆

Laravelで異なるオリジンからAPIを叩く

2024/03/21に公開

やりたいこと

Laravelで、異なるオリジンのWebサービスからAPIを叩けるようにしたい。

試してみる

環境

  • バックエンド
    Laravel 10
    http://127.0.0.1:8000で起動
    ※Laravel7以前と以降で、cors設定の方法が変わっているので注意が必要。
  • フロントエンド
    next.js 14
     http://localhost:3000で起動

検証用のControllerを用意

TestController.php

public function get()
{
    return response()->json(['message' => 'hello']);
}

public function post(Request $request)
{
    return response()->json(['message' => $request->message]);
}

Next.js側でAPIを叩くボタンを置く

<Button
  type="button"
  onClick={() => {
    axios
      .get("http://127.0.0.1:8000/api/test")
      .then((res) => {
        console.log(res);
      })
      .catch((error) => {
        console.log(error);
      });
  }}
>
  Get
</Button>
    <Button
      type="button"
      onClick={() => {
        axios
          .post(
            "http://127.0.0.1:8000/api/test",
            {
              message: "test",
            },
          )
          .then((res) => {
            console.log(res);
          })
          .catch((error) => {
            console.log(error);
          });
      }}
    >
      Post
    </Button>

検証① api.phpでルートを用意して叩いてみる。

api.php

Route::get('test', [TestController::class, 'get']);
Route::post('test', [TestController::class, 'post']);

とする。
api.phpは、Middlewareとして、VerifyCsrfToken.phpを通過しないので、
cors.phpで許可されていれば利用可能なはず。
つまり、
VerifyCsrfToken.phpは、

protected $except = [
    //
];

のままでOK。
※web.phpに書いたルートを使う場合は、当該ルートをここに記載する必要がある。

そのままGETとPOSTボタンを押してみる。

結果

GETもPOSTもできた。

検証② cors.phpでブロックしてみる。

config/cors.php
を見ると、おそらく、下記のようになっている。
今回使っているpathsは、api/*に該当するし、
allowed_methods,allowed_originsは全部許可になっている。

cors.php

'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,

なので、
これを下記のように書き換えてみる。

cors.php

'paths' => [''],//←ここを空にした
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,

これで、先ほど用意したルートはcorsに該当しないため、弾かれるはず。

変更を反映するために、

php artisan config:cache

を打つ。これをしないと、configの変更が反映されない。

結果


ちゃんと弾かれた。

なお、同様に、

'allowed_origins' => [''],

のように、許可するオリジンを消しても、弾かれた。

検証③ CORSで、originを指定してみる。

cors.phpを、

'allowed_origins' => ['http://localhost:3000'],

にして、http://localhost:3000のみで、CORSを許可してみる。
変更したら、忘れず、php artisan config:cache。

結果

GETも、POSTもできる。

ちなみに、

'allowed_origins' => ['http://localhost:3000/'],

のように、最後に/を加えると、ブロックされるので注意。

検証④ allowed_methodsを指定してみる。

cors.phpを、

'allowed_methods' => ['GET'],

にしてみる。
これだと、GETしかできなくなるのでは?
php artisan config:cacheをしてからGETとPOSTをしてみる。

結果

GETも、POSTもできる。
あれ?

検証⑤ PUTとDELETEもやってみる。

next.js側で、GET,POSTに加えて、PUTとDELETEも用意。

<Button
  type="button"
  onClick={() => {
    axios
      .put(
        "http://127.0.0.1:8000/api/test",
        {
          message: "put-test",
        }
      )
      .then((res) => {
        console.log(res);
      })
      .catch((error) => {
        console.log(error);
      });
  }}
>
  Put
</Button>
    <Button
      type="button"
      onClick={() => {
        axios
          .delete(
            "http://127.0.0.1:8000/api/test"

          )
          .then((res) => {
            console.log(res);
          })
          .catch((error) => {
            console.log(error);
          });
      }}
    >
      Delete
    </Button>

laravel側でも、
api.php

Route::get('test', [TestController::class, 'get']);
Route::post('test', [TestController::class, 'post']);
Route::put('test', [TestController::class, 'put']);
Route::delete('test', [TestController::class, 'destroy']);

TestController.php

public function get()
{
    return response()->json([
        'message' => 'Hello',
    ]);
}

public function post(Request $request)
{
    return response()->json([
        'message' => 'post:'.$request->message,
    ]);
}

public function put(Request $request)
{
    return response()->json([
        'message' => 'put:'.$request->message,
    ]);
}

public function destroy()
{
    return response()->json([
        'message' => 'Deleted',
    ]);
}

として、PUTとDELETEも送ってみる。
で、
cors.phpは、

'allowed_methods' => ['GET'],

で、GETのみ許可。

php artisan config:cacheをしてからGET,POST,PUT,DELETEをしてみる。

結果

GET→〇
POST→〇
PUT→✕
DELETE→✕

検証⑥ PUTを許可してみる。

cors.phpで、

'allowed_methods' => ['PUT'],

にして、
php artisan config:cacheをしてからGET,POST,PUT,DELETEをしてみる。

結果

GET→〇
POST→〇
PUT→〇
DELETE→✕

検証⑦ DELETEを許可してみる。

cors.phpで、

'allowed_methods' => ['DELETE'],

にして、
php artisan config:cacheをしてからGET,POST,PUT,DELETEをしてみる。

結果

GET→〇
POST→〇
PUT→✕
DELETE→〇

つまり、GETとPOSTは常に許可されているっぽい。
GETやPOSTはダメだけど、PUTやDELETEならOKって場面はあんまりなさそうだから、まぁいいのかな。

まとめ

  • cors.phpを編集したときは、反映させるのに、php artisan config:cacheが必要。
  • methodは、originが許可されていれば、GETとPOSTは常に許可されてるっぽい。

おまけ

Cookieを含むリクエストを送る場合は、
cors.php

 'supports_credentials' => true,

にして、
next.js側で、
axiosのオプションに、

{ withCredentials: true }

が必要なよう。
これは、LaravelSanctumによるSPA認証を行うなら、必要になってくる。
おそらく、Sanctumを使うなら、上記だけでは設定は不足すると思われるが、
本記事ではそこまで触れないので、
公式ドキュメントを参照。

注意が必要なのは、Sanctumを使わないにしても、
cors.php

 'supports_credentials' => false,

なのに、
axiosのオプションに、

{ withCredentials: true }

にして、フロントエンドからアクセスすると、pathやoriginで許可していても弾かれる。

Discussion