Skip to content

[Security] Per-route firewall security definition #62703

@tilaven

Description

@tilaven

Description

Coming back from Laravel (11/12), one thing that still feels missing in Symfony 7.4 is a way to configure which authentication mechanism / firewall is used directly at the route level, close to the controller / route definition, similar to Laravel's route middleware.

Symfony already allows route-level authorization via #[IsGranted] and via access_control rules that can target specific routes by path or route name. However, the authentication setup (firewalls, authenticators, token handlers, etc.) still has to live in config/packages/security.*, which becomes painful in a modular monolith setup where each module wants to encapsulate its own authentication concerns.

As of Symfony 7.4:

  • Authentication is configured via firewalls under security.firewalls in config/packages/security.yaml (or PHP equivalent). A request matches exactly one firewall, typically by URL pattern, host, and/or HTTP method.
  • Access rules are configured via access_control, which can match by path, methods, ip, route name, etc. This controls who can access (roles, PUBLIC_ACCESS, etc.), but still relies on the firewall that already matched the request.
  • Route-level authorization can be expressed with attributes like #[IsGranted(...)], which in 7.4 even gained a methods option to restrict checks to specific HTTP methods. But this only handles authorization; it does not select the firewall or authenticator.
  • Different authentication mechanisms (session, JWT, access tokens, login links, OIDC, etc.) are configured per firewall or as authenticators under a firewall, and are not selectable per route via attributes or configuration.

So while I can say "this route requires ROLE_USER" or "this route is PUBLIC_ACCESS" close to the controller, I cannot say "this route must use the api_jwt firewall / authenticator and nothing else" close to the route itself. That wiring must currently be expressed in security.* for URL patterns, and often involves tweaking shared firewalls.

Comparison with Laravel

In recent Laravel versions you can keep authentication concerns local to modules by defining a middleware class (e.g. JwtTokenValidationMiddleware) in a module and then attaching it directly to routes or route groups:

Route::get('/profile', function () {
    // ...
})->middleware(JwtTokenValidationMiddleware::class);

The key points:

  • The route itself declares which authentication layer applies, which is very natural for modular / package-style route files
  • Modules can ship their own middleware and route definitions without forcing the main application to know about all their internals in a central security config file

Problem in modular monoliths

In a modular monolith using Symfony 7.4 if each module may expose its own API with its own authentication mechanism, for example:

  • Module A: service API key auth
  • Module B: JWT bearer tokens
  • Module C: OIDC / login-link based auth

To wire these up, I have to:

  • Add or adjust one or more firewalls in config/packages/security.* (path patterns, stateless flags, authenticators, token handlers, etc.).
  • Possibly add additional access_control rules.

This has several downsides:

  • The central security.* config ends up knowing a lot about each module and its URL structure.
  • Moving a module, changing its prefix, or extracting it into a package requires touching global security config.
  • Modules cannot really be "plug-and-play" because their authentication cannot be configured close to their routes.

#[IsGranted] and access_control help with authorization, but they do not solve the per-route "which firewall / authenticator" concern.

Feature request

I would like Symfony 7.4+ to support a way to configure the authentication mechanism / firewall per route, co-located with the route definition, similar in spirit to Laravel's route middleware.

Conceptually, something along these lines:

use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
// imaginary attribute / option:
use Symfony\Component\Security\Http\Attribute\UseFirewall;

#[Route('/profile', name: 'api_profile')]
#[UseFirewall('api_jwt')]         // or #[UseAuthenticator(JwtTokenAuthenticator::class)]
#[IsGranted('ROLE_USER')]
public function profile()
{
    // ...
}

or as a Route option

#[Route(
    path: '/profile',
    name: 'api_profile',
    // pseudo-configuration:
    options: ['firewall' => 'api_jwt']
)]
public function profile() { /* ... */ }

I am not proposing a specific concrete API; I am mainly asking whether Symfony would consider bringing the choice of firewall/authenticator closer to the route, similar to how #[IsGranted] brought authorization closer to the route, and similar to Laravel's route middleware.

If there is already a supported pattern to achieve this without pushing module-specific auth details into the central security.* configuration, I would be happy to adopt it instead.

Example

use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
// imaginary attribute / option:
use Symfony\Component\Security\Http\Attribute\UseFirewall;

#[Route('/profile', name: 'api_profile')]
#[UseFirewall('api_jwt')]         // or #[UseAuthenticator(JwtTokenAuthenticator::class)]
#[IsGranted('ROLE_USER')]
public function profile()
{
    // ...
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions