symfony/security-csrf
Symfony Security CSRF component generates and validates CSRF tokens to protect forms and requests from cross-site request forgery. Provides CsrfTokenManager and related tools for secure token handling in Symfony and PHP apps.
Installation:
composer require symfony/security-csrf
Ensure symfony/http-foundation is also installed (required for Request handling).
Basic Setup:
use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
$session = app(SessionInterface::class);
$tokenGenerator = new UriSafeTokenGenerator();
$tokenManager = new CsrfTokenManager($tokenGenerator, $session);
use Symfony\Component\Security\Csrf\SameOriginCsrfTokenManager;
use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
$tokenGenerator = new UriSafeTokenGenerator();
$tokenManager = new SameOriginCsrfTokenManager($tokenGenerator, 'X-CSRF-TOKEN');
First Use Case: Generate a token for a form:
$token = $tokenManager->getToken('form_action_name');
echo '<input type="hidden" name="_csrf_token" value="' . $token . '">';
Validate incoming requests:
$requestToken = $request->request->get('_csrf_token');
if (!$tokenManager->isTokenValid(new CsrfToken('form_action_name', $requestToken))) {
abort(403, 'Invalid CSRF token');
}
Laravel Integration:
CsrfTokenManager:
public function register()
{
$this->app->singleton(CsrfTokenManager::class, function ($app) {
$session = $app->make(SessionInterface::class);
$generator = new UriSafeTokenGenerator();
return new CsrfTokenManager($generator, $session);
});
}
public function handle($request, Closure $next)
{
$tokenManager = app(CsrfTokenManager::class);
$token = $request->input('_csrf_token');
if (!$tokenManager->isTokenValid(new CsrfToken('global', $token))) {
abort(403);
}
return $next($request);
}
Token Generation:
delete_user_123) to scope tokens to specific actions.
$token = $tokenManager->getToken('delete_user_' . $userId);
global) for broad protection.
$token = $tokenManager->getToken('global');
Validation Workflows:
$token = $request->input('_csrf_token');
if (!$tokenManager->isTokenValid(new CsrfToken('form_submit', $token))) {
throw new \RuntimeException('Invalid CSRF token');
}
$token = $request->headers->get('X-CSRF-TOKEN');
if (!$tokenManager->isTokenValid(new CsrfToken('api_action', $token))) {
abort(403);
}
Token Storage:
$tokenManager = new CsrfTokenManager($generator, $session);
use Symfony\Component\Security\Csrf\Storage\SessionStorage;
use Symfony\Component\Security\Csrf\Storage\StorageInterface;
$storage = new SessionStorage($session);
$tokenManager = new CsrfTokenManager($generator, $storage);
For Redis:
$storage = new RedisStorage($redisClient);
Stateless APIs:
SameOriginCsrfTokenManager with headers/cookies:
$tokenManager = new SameOriginCsrfTokenManager(
$generator,
'X-CSRF-TOKEN',
['Referer', 'Origin', 'Sec-Fetch-Site']
);
$token = $request->headers->get('X-CSRF-TOKEN');
if (!$tokenManager->isTokenValid(new CsrfToken('api_action', $token))) {
abort(403);
}
Laravel Middleware Integration:
public function handle($request, Closure $next)
{
$tokenManager = app(CsrfTokenManager::class);
$token = $request->input('_csrf_token') ?? $request->headers->get('X-CSRF-TOKEN');
if ($token && !$tokenManager->isTokenValid(new CsrfToken('global', $token))) {
abort(403);
}
return $next($request);
}
Token in Templates:
@csrf
Create a directive in a service provider:
Blade::directive('csrf', function () {
$token = app(CsrfTokenManager::class)->getToken('global');
return "<input type='hidden' name='_csrf_token' value='{$token}'>";
});
API Gateways:
SameOriginCsrfTokenManager for stateless validation:
$tokenManager = new SameOriginCsrfTokenManager(
$generator,
'X-CSRF-TOKEN',
['Origin', 'Sec-Fetch-Site']
);
public function handle($request, Closure $next)
{
if ($request->is('api/*')) {
$token = $request->headers->get('X-CSRF-TOKEN');
if (!$tokenManager->isTokenValid(new CsrfToken('api', $token))) {
abort(403);
}
}
return $next($request);
}
Laravel Session Compatibility:
SessionInterface can work with Laravel’s session, but ensure the session is started:
$session = app(SessionInterface::class);
$session->start(); // Explicitly start if not auto-started
Token Generator Customization:
UriSafeTokenGenerator for URL-safe tokens (default).use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
$generator = new class implements TokenGeneratorInterface {
public function generateToken(): string
{
return hash('sha256', uniqid(mt_rand(), true));
}
};
Testing:
CsrfTokenManager in tests:
$tokenManager = $this->createMock(CsrfTokenManager::class);
$tokenManager->method('isTokenValid')->willReturn(true);
$this->app->instance(CsrfTokenManager::class, $tokenManager);
$request = new Request([], [], [], [], [], ['HTTP_X_CSRF_TOKEN' => $validToken]);
$this->assertTrue($tokenManager->isTokenValid(new CsrfToken('api', $validToken)));
Performance:
SameOriginCsrfTokenManager to avoid session overhead.Session Dependency:
CsrfTokenManager requires a session. If the session isn’t started, tokens will fail to validate.
Fix: Explicitly start the session or use SameOriginCsrfTokenManager for stateless apps.Token ID Collisions:
global) may lead to collisions if not scoped properly.
Fix: Use action-specific IDs (e.g., delete_user_123).Header Validation Quirks:
SameOriginCsrfTokenManager checks Referer, Origin, and Sec-Fetch-Site headers. Missing or malformed headers can cause false positives.
Fix: Configure allowed headers explicitly:
$tokenManager = new SameOriginCsrfTokenManager($generator, 'X-CSRF-TOKEN', ['Origin']);
Double Validation:
VerifyCsrfToken middleware with Symfony’s token manager can cause double validationHow can I help you explore Laravel packages today?