🎃

Laravel ルート保護とカスタマイズエラーページ

2021/08/29に公開

このページ、Laravelをやっているなら、誰しも一度は見たことがあるでしょう。

404だけでなく、ルート保護のために、開発者が意図的にエラーページへリダイレクトすることがしばしばあります。また、Not Foundではなく、他のメッセージとかにしたいな、と思ったりすることもあります。今回はルート保護とエラーページのカスタマイズについて見ていきます。

エラーページカスタマイズ

エラーの種類により、それぞれ違うページにリダイレクトしたい場合があります。例えば、404はページ・リソースが見つからない、403は権限が足りない、401は認証・ログインしていない、500はサーバーエラーなど、エラーコードにより分けることが想定できるでしょう。

Laravelではこれらのページのブレードファイルが作成されています。それらのファイルを変更したい場合、まずはリソースフォルダーにパブリッシュする必要があります。

php artisan vendor:publish --tag=laravel-errors

これでresources/views/errorsのフォルダーにデフォルトのブレードファイルがコピーされます。実際にエラーページにリダイレクトする時はこちらのフォルダーのファイルを優先的に使われます。デフォルトとして、401, 403, 404, 419, 429, 500, 503のエラーコードと対応するページがあります。

コントローラなどではabort関数を使うことで、これらのエラーページにリダイレクトされます。例えば、abort(400,'不正なアクセスです')など。それで、ブレードビューの中では、abort関数に渡されたメッセージを受け取ることが可能です。ちなみにabort関数は任意のResponseオブジェクトを引数として受け入れられます。

今回は例として、400と405のページを追加します。404.blade.phpからブレードコードをコピーして、タイトル、コードとメッセージを変更します。

@extends('errors::minimal')

@section('title', __('Bad Request'))
@section('code', '400')
@section('message', __($exception->getMessage() ?: 'アクセスが不正です'))

すべてのエラーページはminimal.blade.phpを継承しています。そのため、スタイリングのカスタマイズはこちらとなります。ただ、BootstrapとかのCSSフレームワークは効かないので、ヘッダーのセクションで直接cssを書くのが良いでしょう。

例えば、今回は前のページに戻るボタンをつけようとします。

まずはボタンのブロックを追加します。

<body>
  <div class="full-height flex-center vertical">
    <div class="position-ref flex-center mb-2">
      <div class="code">
        @yield('code')
      </div>
      <div class="message p-1">
        @yield('message')
      </div>
    </div>
    <div class="position-ref flex-center">
      <a href="{{ url()->previous() }}" class="back-btn">戻る</a>
    </div>
  </div>
</body>

少しスタイリングを:

.mb-2 {
      margin-bottom: 2rem;
}

.p-1 {
      padding: 1rem;
}

.back-btn {
      color: #fff;
      background-color: #1e5ebe;
      border-color: #1e5ebe;
      font-weight: 400;
      text-decoration: none;
      border: 1px solid transparent;
      padding: 0.375rem 0.75rem;
      font-size: 0.9rem;
      line-height: 1.6;
      border-radius: 0.25rem;
}

これですべてのエラーページに元のページに戻るリンクが付けられます。

Ajaxリクエストのルート保護

エラーページができましたので、次にルート保護を見ていきます。前節では触れましたが、ここではabort関数を使います。

よくあるパターンの1つとして、とあるルートがajaxリクエストのみを扱っていて、直接ブラウザーからURLを打って行こうとしたら、エラーページにリダイレクトしたい。という場合に、コントローラで次のように書けます:

// とあるコントローラで

if ($request->ajax()) {
    abort(400);
}

あるいは、もっとオススメなやり方として、カスタマイズのミドルウェアを作って処理します。

php artisan make:middleware AjaxOnly

これでapp\Http\Middlewareフォルダーにファイルが作られますが、次のコードを加えます:

namespace App\Http\Middleware;

use Closure;

class AjaxOnly
{
    public function handle($request, Closure $next)
    {
        if (!$request->ajax()) {
            abort(400, 'アクセスが不正です');
        }
        return $next($request);
    }
}

最後に、必要なルートにこのミドルウェアを付ければOKです。

// routes/web.php
Route::get('/ajax_url', 'AjaxController@index')->middleware('ajax.only');

HTTPリクエストのルート保護

もう一つよくあるパターンとして、このルートはPOSTリクエストとか、DELETEリクエストとかのみに使いたい、GETしようとするときはエラーページにリダイレクトしたいと。

一番シンプルなやり方として、HTTPメソッドが不正のエクセプションを利用し、app\Exception\Handler.phpファイルを編集します。

use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;

// ...
    public function render($request, Exception $exception)
    {
        if ($exception instanceof MethodNotAllowedHttpException) {
            abort(405, '不正なリクエストです');
        }

        return parent::render($request, $exception);
    }

web.phpなどのルートファイルに対応するHTTPメソッドが定義されていない場合、MethodNotAllowedHttpExceptionとのエクセプションが出るため、一概に405のページにリダイレクトします。

以上です!

Discussion