fantoine/csrf-route-bundle
A Symfony bundle that adds CSRF protection to your routes, helping guard state-changing requests against cross-site forgery with minimal configuration and easy integration into existing controllers.
This is a Symfony 2.x bundle, not a Laravel package, so treat it as something you'll read the source of more than the docs (the README is thin, there are 2 stars and 0 dependents). The install path is the classic Symfony2 one:
composer require fantoine/csrf-route-bundle:dev-master
Then register the bundle in app/AppKernel.php:
$bundles = array(
// ...
new Fantoine\CsrfRouteBundle\FantoineCsrfRouteBundle(),
);
If your app is Symfony 3.4+/4+ with config/bundles.php, assume you'll need a fork or a shim — the bundle predates that layout.
Where to look first, in order:
DependencyInjection/Configuration.php — the actual config keys and defaults. Ignore the README until you've read this.Resources/config/services.* — shows whether protection is implemented as a kernel.request listener, a router decorator, or both. This tells you where the token is read from (query, request, header).Routing/ or Annotation/ — how a route opts in (route option vs. annotation). This is the API you'll actually type every day.composer.json — the Symfony version constraints; that's your real compatibility answer.First use case: a GET "delete" link on an index page. Instead of a POST form with a hidden _token, you want GET /users/42/delete?_token=abc123 to be rejected unless the token matches the one in the session. Generate the URL with the token appended, let the listener validate, and return 403 (or redirect with a flash) on mismatch.
Laravel mapping, for orientation: this is VerifyCsrfToken middleware + @csrf, except (a) the token rides in the URL instead of the body, and (b) it's opt-in per route rather than global. The closer Laravel analogue for what this bundle is actually good at is URL::temporarySignedRoute().
Route-option pattern (most likely shape). Protection is declared on the route, which keeps controllers clean:
app_user_delete:
path: /users/{id}/delete
defaults: { _controller: AppBundle:User:delete }
options: { csrf_protected: true } # confirm the exact key in the bundle source
The listener then resolves the matched route, reads the option, and compares the incoming token against the session token. Verify the option name — if it's wrong, protection silently does nothing, which is the worst failure mode here.
Generating protected URLs. If the bundle ships a Twig helper or a router decorator, use it. If not, do it explicitly and wrap it in one place so you can change it later:
// Symfony 2.4+
$token = $this->get('security.csrf.token_manager')->getToken('route_delete')->getValue();
$url = $this->generateUrl('app_user_delete', ['id' => $id]) . '?_token=' . $token;
Older 2.x codebases used form.csrf_provider with isCsrfTokenValid($intention, $token). Check which one the bundle's service definition references before you write calling code against it.
Keep intentions narrow. CSRF tokens are scoped to an intention string and to the session. One intention per action family (route_delete, route_export) means a token leaked from a harmless page can't be replayed against a destructive one.
Don't double-protect. Symfony forms already inject and validate _token. Apply this bundle only to routes that aren't form POSTs: delete links, "mark as read", export/download triggers, one-click toggles.
Testing. Two functional tests per protected route: no token → assert 403/400; valid token from a session-bound client → assert the action ran. Get the token by scraping or by calling the token manager on the test client's container, not by hardcoding.
Migration seam. Put "is this route protected?" and "is this token valid?" behind your own thin service that delegates to the bundle. When you eventually replace the bundle with CsrfTokenManagerInterface + UriSigner, you change one class instead of every controller.
ContainerAware, getRequest(), and deprecated CSRF provider APIs. Fork it into src/ and drop the composer dependency rather than fighting version constraints.Referer headers, browser history, access logs, proxy logs, and any link a user copies. That's an accepted trade-off for low-risk GET actions and unacceptable for anything destructive or account-scoped.Cache-Control: private, no-store or render the link via ESI.kernel.request with a priority above the router? (2) does the intention string match exactly? (3) is the session started before validation? (4) is the token read from query while you're sending it in the body (or vice versa)? (5) is the route option name the one the bundle expects?services.yml. Never edit vendor code — you'll be re-forking it forever.CsrfTokenManagerInterface plus a csrf_token() Twig helper covers this without a third-party bundle. For GET links that mutate state, signed URLs (UriSigner — the direct analogue of Laravel's URL::signedRoute) are a better fit than CSRF tokens, because they bind the whole URL and expire.dev-master to a commit hash and record in your README/ADR that this dependency is unmaintained, so the next person doesn't discover it during an upgrade.How can I help you explore Laravel packages today?