Laravelのweb.phpとapi.phpの違いを調べるために外からPOSTしてみた

Laravel

Laravel Mixを使用した

Laravelは、Laravel Mixを使用して簡単にVue.jsReact.jsなどのフロントエンドフレームワークと組み合わせて作ることができます!

特にVue.jsSCSSの環境はデフォルトで提供されているため、npm installまたは、yarn installするだけで一瞬で環境が作れちゃうのはとても魅力的です。

今回はそのLaravel Mixを使用して、LaravelではAPIとして処理する時に使用するapi.phpについてweb.phpとの違いを調べたので記事にしたいと思います。

 

web.phpとapi.phpの違い

Laravelのルーティングファイルには、web.phpapi.phpがあるのはご存知かと思います。

通常Laravelのbladeを使用して書く場合は、web.phpを使用します。

web.phpで定義したルートには、普段からCSRF保護などの機能が有効になっています。

それでは今回のようにLaravel Mixを使用して、api.phpにLaravelのAPIの処理を記述する場合は、CSRF保護などの機能はどうなのでしょうか?

気になったので調べてみました。

 

api.phpは外からPOSTできるの?

CSRF保護が有効なら、api.phpに定義したルートには外からPOSTできないはずなので実際に試してみました。

 

テスト用のエンドポイントを開ける

こちらのようにapi.phpへテスト用のエンドポイントを定義します。

routes/api.php

Route::group(['middleware' => ['api']], function () {
    Route::post('test', 'TestController@test');
});

 

送られてきた文字列にtest_okを文字列連結して返すようなサンプルの処理を書きます。

app/Http/Controllers/TestController.php

class TestController extends Controller
{
    public function test(Request $request) {
        return $request['test'] . 'test_ok';
    }
}

 

PythonでPOSTしてみた

テスト用に作成したエンドポイントへPOSTするのですが、方法はPostmanなど何でも大丈夫です。

僕はPythonが楽だったので以下のように試しにPOSTしてみました。

>>> import requests
>>> response = requests.post('http://127.0.0.1:8000/api/test', data={"test": "test_key"})
>>> print(response.text)
test_keytest_ok

 

上記の結果から、デフォルトのapi.phpだと外部からもアクセスできてしまいます。

CSRFの保護は無効なようです。

 

web.phpの場合

web.phpはデフォルトでCSRF保護の機能が有効なはずですが、心配になってきたのでこちらも念の為外からPOSTしてみたので掲載しておきます。

 

テスト用のエンドポイントを開ける

先程同様にして、web.phpへテスト用のエンドポイントを定義します。

routes/web.php

Route::post('test2', 'TestController@test');

 

app/Http/Controllers/TestController.php

class TestController extends Controller
{
    public function test2(Request $request) {
        return $request['test'] . 'test_ok';
    }
}

 

PythonでPOSTする

先程と同様、PythonでPOSTしてみます。

>>> import requests
>>> response = requests.post('http://127.0.0.1:8000/test2', data={"test": "test_key"})
>>> print(response.text)
.
.
<!doctype html>
<html lang="en">
    <head>
        <title>Page Expired</title>

 

web.phpでは、「Page Expired」が表示されてCSRF保護の機能がちゃんと有効になっていました。

 

web.phpとapi.phpのデフォルトでの動作

web.phpはCSRF保護が有効で、api.phpはCSRF保護が無効になっていることがわかりました。

改めて考えてみると当然と言えば当然ですよね。

api.phpは当然APIでの処理を書くところなので、外からアクセスできないと使えないですもんね。

 

それでは、web.phpとapi.phpのデフォルトでの動作の違いは、どこの設定によるものなのでしょうか。

 

app/Http/Kernel.phpに設定が記述されている

app/Http/Kernel.phpにweb.phpとapi.phpのそれぞれの設定がミドルウェアとして組み込まれています。

このようにデフォルトでは以下のような設定になっています。

 

app/Http/Kernel.php

protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];

 

api.phpには、VerifyCsrfTokenのミドルウェアが入っていないため機能していなかったのですね。

 

Laravel Mixを使用したLaravelのAPIについて考える

今回のようにLaravel Mixを使用して、フロントはVue.jsやReact.jsで作成し、LaravelのAPIを作成する場合を考えます。

通常フロントとサーバーサイドのAPIを分けて作る場合は別サーバーで作成するため、CSRFでの認証はかけられず、IP制御やその他の認証で外からのアクセスを制御するかとは思います。

しかし、今回はLaravel Mixを使用しフロントとサーバーサイドのAPIを使用して作成しているため、同サーバ内であり、かつbladeも混在させて作成することも可能です。

そのためIP制御等でもいいのですが、面倒なのでapi.phpにもCSRFを有効にしたいと思ったわけです。

 

api.phpでもCSRFを有効にした

app/Http/Kernel.phpapiのところにVerifyCsrfTokenを入れればCSRFが有効になるわけですが、VerifyCsrfTokenCookieSessionらへんの機能を使用するため、それもミドルウェアとして含めないとエラーになります。

こちらのように、とりあえずweb.phpのやつと同じように全部入れればちゃんと動きました。

 

app/Http/Kernel.php

protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
            'throttle:60,1',
            'bindings',
        ],
    ];

 

これでVue.jsやReact.jsもすぐに使えるし、LaravelでAPIも使えるし、セキュリティ関連もとりあえずCSRFは有効になりました。

 

ちなみに、bladeでHTMLのheadに以下のCSRFのtokenを入れましょう!

<meta name="csrf-token" content="{{ csrf_token() }}">

 

フロントからはAxiosを使えば、ヘッダーにいちいち手動でCSRFを指定しなくて済むのでとても楽です。

resources/js/bootstrap.jsで自動的にAxiosの処理の中でCSRFのtokenを付与してくれているのです。

 

参考サイト

ルーティング 5.5 Laravel

 

 

コメント