Skip to main content
Version: 7.x

User Provider

Customize how the current user is identified in audit entries

A user provider returns information about the current user for audit entries.

📝 Interface

namespace DH\Auditor\User;

interface UserProviderInterface
{
public function __invoke(): ?UserInterface;
}

The provider must return a UserInterface or null:

namespace DH\Auditor\User;

interface UserInterface
{
public function getIdentifier(): ?string;
public function getUsername(): ?string;
}

📦 Built-in Provider

The default DH\AuditorBundle\User\UserProvider:

public function __invoke(): ?UserInterface
{
$tokenUser = $this->getTokenUser();
$impersonatorUser = $this->getImpersonatorUser();

// Get identifier if getId() method exists
$identifier = null;
if ($tokenUser instanceof SymfonyUserInterface) {
if (method_exists($tokenUser, 'getId')) {
$identifier = $tokenUser->getId();
}
$username = $tokenUser->getUserIdentifier();
}

// Track impersonation
if ($impersonatorUser instanceof SymfonyUserInterface) {
$username .= '[impersonator '.$impersonatorUser->getUserIdentifier().']';
}

if (null === $identifier && null === $username) {
return null;
}

return new User((string) $identifier, $username);
}

🖥️ Console Commands

For CLI commands, the bundle provides DH\AuditorBundle\User\ConsoleUserProvider which automatically tracks the command name:

public function __invoke(): ?UserInterface
{
if (null === $this->currentCommand) {
return null;
}

// Command name is used as both ID and username
return new User(
$this->currentCommand, // e.g., 'app:import-users'
$this->currentCommand
);
}

TIP

This allows you to:

  • 🔍 Identify which command made specific changes
  • 🔎 Filter audit entries by command in the viewer
  • 📊 Track automated processes separately from user actions

Example audit entries:

  • app:import-users - Import command
  • doctrine:fixtures:load - Fixtures loading
  • app:sync-products - Sync command

🔧 Creating a Custom Provider

Basic Example

<?php

namespace App\Audit;

use DH\Auditor\User\User;
use DH\Auditor\User\UserInterface;
use DH\Auditor\User\UserProviderInterface;

class CustomUserProvider implements UserProviderInterface
{
public function __construct(
private readonly MyAuthService $authService,
) {}

public function __invoke(): ?UserInterface
{
$user = $this->authService->getCurrentUser();

if (null === $user) {
return null;
}

return new User(
(string) $user->getId(),
$user->getEmail()
);
}
}

Registration

# config/packages/dh_auditor.yaml
dh_auditor:
user_provider: 'App\Audit\CustomUserProvider'

The service is auto-wired. If you need explicit configuration:

# config/services.yaml
services:
App\Audit\CustomUserProvider:
arguments:
- '@App\Security\MyAuthService'

📚 Examples

🔑 API Token Authentication

<?php

namespace App\Audit;

use App\Security\ApiTokenManager;
use DH\Auditor\User\User;
use DH\Auditor\User\UserInterface;
use DH\Auditor\User\UserProviderInterface;

class ApiUserProvider implements UserProviderInterface
{
public function __construct(
private readonly ApiTokenManager $tokenManager,
) {}

public function __invoke(): ?UserInterface
{
$token = $this->tokenManager->getCurrentToken();

if (null === $token) {
return null;
}

return new User(
$token->getClientId(),
sprintf('API: %s', $token->getClientName())
);
}
}

🌐 External OAuth/SSO

<?php

namespace App\Audit;

use DH\Auditor\User\User;
use DH\Auditor\User\UserInterface;
use DH\Auditor\User\UserProviderInterface;
use Symfony\Component\HttpFoundation\RequestStack;

class OAuthUserProvider implements UserProviderInterface
{
public function __construct(
private readonly RequestStack $requestStack,
) {}

public function __invoke(): ?UserInterface
{
$request = $this->requestStack->getCurrentRequest();

if (null === $request) {
return null;
}

// User info from OAuth headers
$userId = $request->headers->get('X-User-Id');
$userName = $request->headers->get('X-User-Email');

if (null === $userId) {
return null;
}

return new User($userId, $userName ?? 'unknown');
}
}

🔀 Combining Multiple Sources

<?php

namespace App\Audit;

use App\Security\ApiTokenManager;
use DH\Auditor\User\User;
use DH\Auditor\User\UserInterface;
use DH\Auditor\User\UserProviderInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

class CompositeUserProvider implements UserProviderInterface
{
public function __construct(
private readonly TokenStorageInterface $tokenStorage,
private readonly ApiTokenManager $apiTokenManager,
) {}

public function __invoke(): ?UserInterface
{
// Try Symfony security first
$token = $this->tokenStorage->getToken();
if (null !== $token && null !== $token->getUser()) {
$user = $token->getUser();
return new User(
method_exists($user, 'getId') ? (string) $user->getId() : '',
$user->getUserIdentifier()
);
}

// Fall back to API token
$apiToken = $this->apiTokenManager->getCurrentToken();
if (null !== $apiToken) {
return new User(
$apiToken->getClientId(),
'API: ' . $apiToken->getClientName()
);
}

return null;
}
}

🚀 Next Steps