symfony/security-csrf
Symfony Security CSRF component provides CsrfTokenManager to generate, store, and validate CSRF tokens, protecting forms and requests against cross-site request forgery. Integrates cleanly with Symfony apps and can be used standalone in PHP projects.
Install the Package
composer require symfony/security-csrf
For Laravel, ensure compatibility with your Symfony components (e.g., symfony/http-foundation).
Register the Service
Bind the CsrfTokenManagerInterface to a concrete implementation in Laravel’s service container (e.g., app/Providers/AppServiceProvider.php):
use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
public function register()
{
$this->app->singleton(CsrfTokenManagerInterface::class, function ($app) {
$tokenGenerator = new UriSafeTokenGenerator();
return new CsrfTokenManager($tokenGenerator);
});
}
Generate a Token
Inject CsrfTokenManagerInterface into a controller or service:
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
public function showForm(CsrfTokenManagerInterface $csrfTokenManager)
{
$token = $csrfTokenManager->getToken('form_token')->getValue();
return view('form', ['csrf_token' => $token]);
}
Validate a Token In a form handler or API endpoint:
public function submitForm(Request $request, CsrfTokenManagerInterface $csrfTokenManager)
{
$token = $request->request->get('_csrf_token');
if (!$csrfTokenManager->isTokenValid(new CsrfToken('form_token', $token))) {
throw new \Symfony\Component\Security\Core\Exception\AccessDeniedException('Invalid CSRF token.');
}
// Process the form...
}
Laravel Integration (Optional)
Extend Laravel’s built-in CSRF middleware (app/Http/Middleware/VerifyCsrfToken.php) to use Symfony’s token manager:
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
protected function validateCsrfToken($request, $exception)
{
$token = $request->input('_csrf_token');
if (!$this->csrfTokenManager->isTokenValid(new CsrfToken('laravel_csrf_token', $token))) {
throw $exception;
}
}
<input type="hidden" name="_csrf_token" value="{{ $csrf_token }}">
$this->validate($request, ['_csrf_token' => 'required|string']);
if (!$this->csrfTokenManager->isTokenValid(new CsrfToken('form_token', $request->input('_csrf_token')))) {
abort(403, 'Invalid CSRF token.');
}
UriSafeTokenGenerator (base64-encoded random bytes) and stored in memory or a session.CsrfTokenStorageInterface for database/Redis storage:
use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
class DatabaseTokenStorage implements TokenStorageInterface
{
public function getToken($tokenId): ?CsrfToken
{
// Fetch from database...
}
public function deleteToken($tokenId): void
{
// Delete from database...
}
}
CsrfTokenManager:
$this->app->singleton(CsrfTokenManagerInterface::class, function ($app) {
$storage = new DatabaseTokenStorage();
$tokenGenerator = new UriSafeTokenGenerator();
return new CsrfTokenManager($tokenGenerator, $storage);
});
SameSite attributes and headers (e.g., Sec-Fetch-Site) for stateless validation:
use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
use Symfony\Component\Security\Csrf\TokenStorage\CookieTokenStorage;
$tokenStorage = new CookieTokenStorage(
$requestStack,
'XSRF-TOKEN',
'X-CSRF-TOKEN',
['same_site' => 'lax']
);
$csrfTokenManager = new CsrfTokenManager(new UriSafeTokenGenerator(), $tokenStorage);
public function handle($request, Closure $next)
{
$token = $this->csrfTokenManager->getToken('api_token')->getValue();
$request->headers->set('X-CSRF-Token', $token);
return $next($request);
}
$token = $csrfTokenManager->getToken('form_token');
$csrfTokenManager->deleteToken('form_token'); // Invalidate old token
// Generate for login form
$loginToken = $csrfTokenManager->getToken('login_token')->getValue();
// Generate for profile form
$profileToken = $csrfTokenManager->getToken('profile_token')->getValue();
Middleware Integration Create a custom middleware to validate CSRF tokens:
namespace App\Http\Middleware;
use Closure;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
class CsrfTokenMiddleware
{
public function __construct(protected CsrfTokenManagerInterface $csrfTokenManager)
{
}
public function handle($request, Closure $next)
{
if ($request->isMethod('post') && !$request->has('_csrf_token')) {
abort(403);
}
$token = $request->input('_csrf_token');
if (!$this->csrfTokenManager->isTokenValid(new CsrfToken('laravel_csrf_token', $token))) {
abort(403, 'Invalid CSRF token.');
}
return $next($request);
}
}
Register it in app/Http/Kernel.php:
protected $middleware = [
// ...
\App\Http\Middleware\CsrfTokenMiddleware::class,
];
Blade Directives Create a Blade directive for token generation:
Blade::directive('csrfToken', function ($expression) {
$token = app(CsrfTokenManagerInterface::class)->getToken($expression)->getValue();
return "<?php echo '{$token}'; ?>";
});
Usage in views:
<input type="hidden" name="_csrf_token" value="{{ csrfToken('form_token') }}">
API Resource Protection For APIs, use a header-based approach:
// Middleware
public function handle($request, Closure $next)
{
$token = $request->header('X-CSRF-TOKEN');
if (!$this->csrfTokenManager->isTokenValid(new CsrfToken('api_token', $token))) {
abort(403);
}
return $next($request);
}
Unit Testing Tokens
Mock CsrfTokenManager in tests:
$tokenManager = $this->createMock(CsrfTokenManagerInterface::class);
$tokenManager->method('isTokenValid')->willReturn(true);
$this->app->instance(CsrfTokenManagerInterface::class, $tokenManager);
Integration Testing Test token validation with real requests:
public function test_csrf_protection()
{
$response = $this->post('/submit-form', [
'_csrf_token' => $this->csrfTokenManager->getToken('form_token')->getValue(),
'data' => 'test',
]);
$response->assertStatus(200);
}
Token Storage Assumptions
CsrfTokenManager defaults to in-memory storage, which is not persistent across requests.SessionTokenStorage or a custom implementation).Token ID Collisions
'form_token') for multiple forms can cause validation failures if tokens are rotated.How can I help you explore Laravel packages today?