How to setup IP restriction in Laravel Horizon and avoid 403 access denied error

Horizon is a great package for managing your Redis queues within Laravel. However I recently came across the 403 Access Denied problem when trying to access the dashboard whilst on my production server.

I had decided to use IP restriction to limit who has access to the dashboard via middleware, but I had incorectly configured the Gate and it was a classic case of read the documentation more!

This post will address 2 points:

  1. How to restrict access to Horizon by IP address
  2. How to fix the common 403 access denied error when working in production

Restrict access by IP address

In the config/horizon.php config file it allows you to specify the middleware you want to run. By default the web auth guard is enabled, but we can create our own middleware and do the IP checking in there.

Start by creating our own middleware. php artisan make:middleware HorizonAccess. Inside the class add the following:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class HorizonAccess
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        if (!in_array($request->ip(), explode(',', config('app.horizon_allowed_ips')))) {
            abort(403);
        }
        
        return $next($request);
    }
}

What were doing here is taking the IP address of the current request and seeing if its not in the array of allowed IP addresses that we can set in the .env file and also in the config/app.php config file.

Next in your .env file add the following: HORIZON_ALLOWED_IPS="111.111.111.111, 222.222.222.222". Replacing them with your desired IP addresses.

After adding that in, head over to config/app.php file and add in this value at the end of the file: 'horizon_allowed_ips' = env('HORIZON_ALLOWED_IPS', null), the benefit of doing it this way is that you can simply edit your .env file when you want to add or remove IP addresses, instead of hard coding them in.

We also need to add our new middleware into app/Http/Kernel.php. In the route Middleware section add the following to the bottom of the array:

protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
    ...
    'horizon' => \App\Http\Middleware\Horizon::class,
];

I've called mine 'horizon' but feel free to call yours whatever you like.

Finally we need to make some minor changes to the app/Providers/HorizonServiceProvider.php file. At the bottom there is an authorization gate which you should configure when you want to use this in production or any other environment that isn't local. Make the following changes to it:

protected function gate()
{
    Gate::define('viewHorizon', function ($user = null) {
        return true;
    });
}

So this change allows anyone to access the horizon dashboard, but since we want to restrict access by IP address and not a logged in user, we need to set the $user variable to be null so that Laravel doesn't check that the user is logged in. This is what causes the 403 access denied error in production. See the documentation.

Remember that Laravel automatically injects the authenticated user into the gate closure. If your application is providing Horizon security via another method, such as IP restrictions, then your Horizon users may not need to "login". Therefore, you will need to change function ($user) closure signature above to function ($user = null) in order to force Laravel to not require authentication.

One last thing to do is to apply the 'horizon' middleware to in the config/horizon.php file, in the middleware array, add in 'horizon' or whatever you called in the the $routeMiddleware in app/Http/Kernel.php.

Deploy your code and try to access the horizon dashboard. And to test it 403's you, try from a different IP address - perhaps using your phone and mobile data instead of WiFi.

More Posts

Laravel PDF API

As part of my day job I was required to make PDF's from HTML templates and expose this via an...

Laravel Queue Delays Not Working

Using Laravel 5.7 queues along with Redis I found that whilst the events were being dispatched and handled by the...