Security Provider
Customize IP address and context detection for audit entries
A security provider returns contextual security information for audit entries.
đ Interfaceâ
namespace DH\Auditor\Security;
interface SecurityProviderInterface
{
public function __invoke(): array;
}
Returns an array with two elements:
return [
$clientIp, // string|null - Client IP address
$firewallName, // string|null - Firewall or context name
];
đĻ Built-in Providerâ
The default DH\AuditorBundle\Security\SecurityProvider:
public function __invoke(): array
{
$request = $this->requestStack->getCurrentRequest();
if (!$request instanceof Request) {
return [null, null];
}
$firewallConfig = $this->firewallMap->getFirewallConfig($request);
return [
$request->getClientIp(),
$firewallConfig?->getName(),
];
}
đ§ Creating a Custom Providerâ
Basic Exampleâ
<?php
namespace App\Audit;
use DH\Auditor\Security\SecurityProviderInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class CustomSecurityProvider implements SecurityProviderInterface
{
public function __construct(
private readonly RequestStack $requestStack,
) {}
public function __invoke(): array
{
$request = $this->requestStack->getCurrentRequest();
if (null === $request) {
return [null, null];
}
return [
$request->getClientIp(),
'my-app',
];
}
}
Registrationâ
# config/packages/dh_auditor.yaml
dh_auditor:
security_provider: 'App\Audit\CustomSecurityProvider'
đ Examplesâ
đ Behind a Proxy/Load Balancerâ
IMPORTANT
When behind a proxy, the client IP may be in forwarded headers rather than the direct connection IP.
<?php
namespace App\Audit;
use DH\Auditor\Security\SecurityProviderInterface;
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
class ProxyAwareSecurityProvider implements SecurityProviderInterface
{
public function __construct(
private readonly RequestStack $requestStack,
private readonly FirewallMap $firewallMap,
) {}
public function __invoke(): array
{
$request = $this->requestStack->getCurrentRequest();
if (!$request instanceof Request) {
return [null, null];
}
// Check proxy headers in order of preference
$clientIp = $request->headers->get('X-Real-IP')
?? $request->headers->get('X-Forwarded-For')
?? $request->getClientIp();
// Handle X-Forwarded-For with multiple IPs
if (str_contains($clientIp ?? '', ',')) {
$clientIp = trim(explode(',', $clientIp)[0]);
}
$firewallConfig = $this->firewallMap->getFirewallConfig($request);
return [$clientIp, $firewallConfig?->getName()];
}
}
âī¸ Cloudflareâ
<?php
namespace App\Audit;
use DH\Auditor\Security\SecurityProviderInterface;
use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
class CloudflareSecurityProvider implements SecurityProviderInterface
{
public function __construct(
private readonly RequestStack $requestStack,
private readonly FirewallMap $firewallMap,
) {}
public function __invoke(): array
{
$request = $this->requestStack->getCurrentRequest();
if (!$request instanceof Request) {
return [null, null];
}
// Cloudflare provides original IP in CF-Connecting-IP
$clientIp = $request->headers->get('CF-Connecting-IP')
?? $request->getClientIp();
$firewallConfig = $this->firewallMap->getFirewallConfig($request);
return [$clientIp, $firewallConfig?->getName()];
}
}
đŠī¸ AWS ALB / API Gatewayâ
<?php
namespace App\Audit;
use DH\Auditor\Security\SecurityProviderInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class AwsSecurityProvider implements SecurityProviderInterface
{
public function __construct(
private readonly RequestStack $requestStack,
) {}
public function __invoke(): array
{
$request = $this->requestStack->getCurrentRequest();
if (null === $request) {
return [null, null];
}
// AWS ALB/API Gateway headers
$clientIp = $request->headers->get('X-Forwarded-For');
if (null !== $clientIp && str_contains($clientIp, ',')) {
// First IP is the original client
$clientIp = trim(explode(',', $clientIp)[0]);
}
// Use API Gateway stage as context
$context = $request->headers->get('X-Amzn-Api-Gateway-Stage', 'unknown');
return [$clientIp ?? $request->getClientIp(), $context];
}
}
đĨī¸ Console Context Awareâ
<?php
namespace App\Audit;
use DH\Auditor\Security\SecurityProviderInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class ConsoleAwareSecurityProvider implements SecurityProviderInterface
{
private ?string $consoleContext = null;
public function __construct(
private readonly RequestStack $requestStack,
) {}
public function __invoke(): array
{
$request = $this->requestStack->getCurrentRequest();
// HTTP context
if (null !== $request) {
return [$request->getClientIp(), 'http'];
}
// Console context
return ['127.0.0.1', $this->consoleContext ?? 'console'];
}
public function setConsoleContext(?string $context): void
{
$this->consoleContext = $context;
}
}
đ Next Stepsâ
- đĄī¸ Role Checker - Customize access control
- đ¤ User Provider - Customize user identification