開発環境等、IPでアクセス制限をかけたい場合
例えばAWSを使っていればセキュリティグループでIP制限をかけたりします。
しかしIP制限をかけるエンドポイントを柔軟に変更したり、複雑な条件によってIP制限をかけたりしたい場合はLaravel側でIP制限をかけた方が良い場合があります。
環境
- PHP 8.0.10
- Laravel 8.58.0
ミドルウェアを作成する
$ php artisan make:middleware Firewall
Laravelではリクエストの検証、フィルタリングする場合はミドルウェアという便利な機能が用意されています。
IP制限をかけるので名前はFirewallにしてみました。
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Closure;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\IpUtils;
final class Firewall
{
    private const ALLOWED_IPS = [
        '127.0.0.1', // 例) ローカルからのアクセスは許可
        '172.17.0.1/16', // 例) サブネットマスクで範囲指定も可能(docker networkのIP range)
    ];
    /**
     * Handle an incoming request.
     *
     * @param Request $request
     * @param Closure $next
     * @return mixed
     * @throws AuthorizationException
     */
    public function handle(Request $request, Closure $next)
    {
        if (config('app.env') === 'local' || $this->isAllowedIp($request->ip())) {
            return $next($request);
        }
        throw new AuthorizationException(sprintf('Access denied from %s', $request->ip()));
    }
    /**
     * @param string $ip
     * @return bool
     */
    private function isAllowedIp(string $ip): bool
    {
        return IpUtils::checkIp($ip, self::ALLOWED_IPS);
    }
}
ロードバランサーを経由している場合
$request->ip(); だとロードバランサーのIPが取得されます。
collect($request->getClientIps())->last(); でクライアントのIPを取得できます。
許可するIPが固定値であれば、Firewallミドルウェアの定数として登録して良いと思います。
環境ごとに変えたい場合は、configファイルや環境変数を利用したり、データベースでIPを管理すると良いでしょう。
IpUtils::checkIp を使うとサブネットマスクを含めて判定してくれるので便利です。
Httpカーネルにミドルウェアを登録する
<?php declare(strict_types=1);
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        // \App\Http\Middleware\TrustHosts::class,
        \App\Http\Middleware\TrustProxies::class,
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];
    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    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' => [
            // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];
    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'firewall' => \App\Http\Middleware\Firewall::class, // ここに追加
    ];
}
全体的に適用したい場合は、 $middleware に追加したり、 $middlewareGroups に追加してもokです。
ルーティングにミドルウェアを設定
routes/web.php や routes/api.php で定義した firewall ミドルウェアを設定します。
<?php declare(strict_types=1);
use Illuminate\Support\Facades\Route;
Route::middleware(['firewall'])->group(function () {
    // IP制限したいエンドポイントをグループ内に定義する
    Route::get('/', function () {
        return view('welcome');
    });
});
動作確認
APP_ENV=local
環境変数 APP_ENV=local の時はアクセス制限せず200レスポンスが返ります。
(ローカル環境でIP制限があるのは面倒ですからね...)
APP_ENV=development
環境変数 APP_ENV=development の時は登録されてないIPでアクセスがあった場合403レスポンスを返してます。
ミドルウェアを使ってIP制限できるようになりました!
開発環境や本番環境、特定の画面だけでIP制限したい場合でも簡単にカスタマイズできると思うのでぜひ使ってみてください。

