Custom Plugin Implementation¶
Create a custom plugin to implement specific business logic:
<?php
namespace App\Security\Firewall\Plugins;
use Kanopi\Firewall\Plugins\AbstractPluginBase;
use Symfony\Component\HttpFoundation\Request;
class ApiKeyValidator extends AbstractPluginBase
{
private array $validApiKeys;
public function __construct(array $metadata = [], array $config = [])
{
parent::__construct($metadata, $config);
// Load API keys from configuration or database
$this->validApiKeys = $metadata['api_keys'] ?? [];
}
protected function defaultName(): string
{
return 'API Key Validator';
}
public function getDescription(): string
{
return 'Validates API keys for authenticated endpoints';
}
public function evaluate(Request $request): bool
{
// Only check API endpoints
if (!str_starts_with($request->getPathInfo(), '/api/')) {
return false;
}
// Check for API key in header or query
$apiKey = $request->headers->get('X-API-Key')
?? $request->query->get('api_key');
if (!$apiKey) {
$this->logger?->warning('Missing API key', [
'ip' => $request->getClientIp(),
'path' => $request->getPathInfo(),
]);
return true; // Block request
}
if (!in_array($apiKey, $this->validApiKeys, true)) {
$this->logger?->warning('Invalid API key', [
'ip' => $request->getClientIp(),
'api_key' => substr($apiKey, 0, 8) . '...',
]);
return true; // Block request
}
return false; // Allow request
}
public function getStatusCode(): int
{
return 401; // Unauthorized
}
}
Register the custom plugin in your configuration:
plugins:
- plugin: "App\\Security\\Firewall\\Plugins\\ApiKeyValidator"
response: block
weight: -150 # Run before rate limiting
enable: true
metadata:
api_keys:
- "sk_live_abcd1234567890"
- "sk_live_efgh0987654321"
Naming a custom plugin¶
defaultName() is what the plugin is called when the configuration declares no name of its own. Override it, and metadata.name still overrides that:
plugins:
- plugin: "App\\Security\\Firewall\\Plugins\\ApiKeyValidator"
response: block
metadata:
name: partner-api-keys
firewall.WARNING: Request blocked {"plugin_name":"partner-api-keys","plugin_type":"App\\Security\\Firewall\\Plugins\\ApiKeyValidator", …}
Override neither and the plugin logs its short class name — ApiKeyValidator.
Upgrading from
getName(). A plugin that implementspublic function getName()itself keeps working exactly as before; that is whydefaultName()is a concrete method rather than an abstract one. Such a plugin simply never seesmetadata.name, because it has taken over the method that reads it. Rename it toprotected function defaultName()to opt in. Seemetadata.name.