bespredel / geo-restrict
Middleware to restrict access to Laravel applications based on IP geolocation.
Requires
- php: >=8.1
- illuminate/support: ^10|^11|^12
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^10.0
README
🚀 Production-ready GeoIP access control for Laravel applications
GeoRestrict is a powerful and flexible Laravel middleware that allows you to control access to your application based on user geolocation.
It supports filtering by country, region, city, ASN, ISP, and provides advanced features like multi-provider fallback, caching, rate limiting, and flexible rule configuration.
✨ Features
- 🌍 Geo-based filtering (country, region, city, ASN, ISP)
- 🔀 Multiple GeoIP providers with fallback support
- ⚡ Built-in caching for performance optimization
- 🚦 Rate limiting for external GeoIP services
- 🧠 Flexible allow/deny rules with callbacks
- ⏱ Time-based access restrictions
- 📍 Route-level targeting (patterns + HTTP methods)
- 🛡 IP whitelist support
- 🌐 Multi-language error responses
- 📊 Logging for allowed and blocked requests
📦 Installation
- Install the package via Composer::
composer require bespredel/geo-restrict
- Publish the config file:
php artisan vendor:publish --provider="Bespredel\GeoRestrict\GeoRestrictServiceProvider" --tag=geo-restrict-config
- (Optional) Publish language files for customization:
php artisan vendor:publish --provider="Bespredel\GeoRestrict\GeoRestrictServiceProvider" --tag=geo-restrict-lang
⚙️ Configuration
The configuration file allows you to fully customize how GeoRestrict behaves.
Example:
return [ 'services' => [ // Example provider with options [ 'provider' => \Bespredel\GeoRestrict\Providers\Ip2LocationIoProvider::class, 'options' => [ 'api_key' => 'your-ip2location-api-key', // required 'lang' => 'en', // optional ], ], // Example provider without options \Bespredel\GeoRestrict\Providers\IpWhoIsProvider::class, // or [ 'provider' => \Bespredel\GeoRestrict\Providers\IpWhoIsProvider::class, 'options' => [], ], // Example array provider [ 'name' => 'ipapi.co', 'url' => 'https://ipapi.co/:ip/json/', 'headers' => [ 'Accept' => 'application/json', ], 'map' => [ 'country' => 'country_code', 'region' => 'region_code', 'city' => 'city', 'asn' => 'asn', 'isp' => 'org', ], ], // Add more services; priority is based on order ], 'geo_services' => [ 'cache_ttl' => 1440, 'rate_limit' => 30, ], 'access' => [ 'rules' => [ 'allow' => [ 'country' => ['RU'], 'region' => [], 'city' => [], 'asn' => [], 'callback' => null, // function($geo) { return ...; } 'time' => [ // ['from' => '08:00', 'to' => '20:00'] ], ], 'deny' => [ 'country' => [], 'region' => [], 'city' => [], 'asn' => [], 'callback' => null, // function($geo) { return ...; } 'time' => [ // ['from' => '22:00', 'to' => '06:00'] ], ], ], ], 'routes' => [ 'only' => [], // ['admin/*', 'api/v1/*'] 'except' => [], 'methods' => [], // ['GET', 'POST'] ], 'excluded_networks' => [ '127.0.0.1', '::1', '10.0.0.0/8', '192.168.0.0/16', '172.16.0.0/12', // Add more as needed ], 'logging' => [ 'blocked_requests' => true, 'allowed_requests' => false, 'channel' => 'geo-restrict', // Use your custom channel name ], 'block_response' => [ 'type' => 'abort', // 'abort', 'json', 'view' 'view' => 'errors.geo_blocked', 'json' => [ 'message' => 'Access denied: your region is restricted.', ], ] ];
Key parameters explained
- services - list of geo-services used to resolve location by IP. Each provider supports only its documented parameters (see table below).
- geo_services.cache_ttl - cache lifetime in minutes (0 disables caching).
- geo_services.rate_limit - max requests per minute per IP to geo services.
- access.rules.allow/deny - allow/deny rules by country, region, ASN, callbacks and time periods.
- logging - enable logging of blocked or allowed requests.
- block_response.type - response type: 'abort', 'json', or 'view'.
- routes.only/except/methods - route and method matching.
- excluded_networks - list of IP networks to exclude from geo restriction.
Supported Providers and Parameters
| Provider | Class | Required Params | Optional Params |
|---|---|---|---|
| IP2Location.io | Ip2LocationIoProvider | api_key, ip | lang |
| ipwho.is | IpWhoIsProvider | ip | api_key |
| ip-api.com | IpApiComProvider | ip | lang |
| ipapi.co | IpApiCoProvider | ip | lang |
Only parameters listed in requiredParams and optionalParams for each provider will be used in the request. If a required parameter is missing, an
error will be thrown and logged. Optional parameters are included only if set in config.
Provider architecture
All geo providers now inherit from AbstractGeoProvider and only need to define:
baseUrl,endpoint(with :param placeholders)requiredParams,optionalParamsresponseMap(array for mapping API fields to standard keys)isValidResponse(array $data)(checks if API response is valid)getErrorMessage(array $data)(returns error message for invalid response)
Example of a minimal provider:
class ExampleProvider extends AbstractGeoProvider { protected ?string $baseUrl = 'https://example.com/'; protected ?string $endpoint = 'api/:ip'; protected array $requiredParams = ['ip']; protected array $optionalParams = ['lang']; protected array $responseMap = [ 'country' => 'country_code', 'region' => 'region', 'city' => 'city', 'asn' => 'asn', 'isp' => 'isp', ]; protected function isValidResponse(array $data): bool { return isset($data['country_code']); } protected function getErrorMessage(array $data): string { return 'example.com: invalid response'; } public function getName(): string { return 'example.com'; } }
This makes it easy to add new providers and ensures all logic is unified and DRY.
🚀 Usage
- Add the middleware to routes:
Route::middleware(['geo-restrict'])->group(function () { // ... });
- Or apply directly:
Route::get('/secret', 'SecretController@index')->middleware('geo-restrict');
🔧 Customization
- Add your geo-services to the
servicesarray; order defines priority. - Use allow/deny rules for flexible filtering.
- For complex cases, use callback functions in rules.
- For localization, use language files.
🌐 Localization and Language Files
GeoRestrict supports multi-language block messages. To customize or add new translations:
- Publish the language files:
php artisan vendor:publish --provider="Bespredel\GeoRestrict\GeoRestrictServiceProvider" --tag=geo-restrict-lang
- Edit files in
resources/lang/vendor/geo-restrict/as needed. Add new locales by creating new folders (e.g.,it,es).
- The block message is automatically shown in the user's country language (based on the country code, if a corresponding language file exists), or in the application's default language.
- To add a new language, create a file like:
resources/lang/it/messages.php
No code changes are required - the language is detected automatically based on the country code (e.g., IT, FR, DE, RU, EN, etc.).
⚡ Cache Management & Tag-based Cache Flush
GeoRestrict uses Laravel's cache for geo-data and rate limiting. If your cache driver supports tags (Redis, Memcached), all geoip cache entries are tagged with geoip.
- To flush all geoip cache entries at once, use the artisan command:
php artisan geo-restrict:clear-cache
You will see:
GeoIP cache flushed.
Note: Tag-based cache flush is only available for Redis and Memcached drivers. For other drivers, cache will not be flushed in bulk.
📊 Logging: Custom Channel Support
GeoRestrict supports logging to a custom channel. By default, all logs (blocked/allowed requests, provider errors, rate limits) go to the main Laravel log. To use a separate channel, set the logging.channel parameter in your config/geo-restrict.php:
'logging' => [ 'blocked_requests' => true, 'allowed_requests' => false, 'channel' => 'geo-restrict', // Use your custom channel name ],
Add a channel to your config/logging.php:
'channels' => [ // ... 'geo-restrict' => [ 'driver' => 'single', 'path' => storage_path('logs/geo-restrict.log'), 'level' => 'info', ], ],
If channel is not set or is null, logs will go to the default log channel.
📄 License
This package is open-source software licensed under the MIT license.
⭐ Support
If you find this package useful, consider giving it a star ⭐ on GitHub.