Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Route Security Bundle Laravel Package

sil/route-security-bundle

SIL Route Security Bundle adds declarative, route-level access control to Symfony apps. Define who can access each route with configurable rules, keeping authorization close to routing and easy to audit without scattering security checks across controllers.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the framing: this is a Symfony bundle, not a Laravel package. There is no service provider, no config/route-security.php, no Blade directives, and no php artisan vendor:publish. You'd only reach for it if you're working inside a Symfony application (or a Symfony-based admin/legacy service sitting next to your Laravel app). If you're looking for the Laravel equivalent, see the mapping table in Gotchas and Tips.

Install and register:

composer require sil/route-security-bundle
// config/bundles.php
return [
    // ...
    Sil\RouteSecurityBundle\SilRouteSecurityBundle::class => ['all' => true],
];

There is almost certainly no Flex recipe (7 stars, 0 dependents), so expect to create the config file by hand rather than getting config/packages/sil_route_security.yaml auto-generated.

Where to look first, in this order:

  1. README.md — for a small bundle like this, the README is the API surface. Read it end to end before anything else.
  2. DependencyInjection/Configuration.php — the real, authoritative list of config keys. README examples drift; this file does not.
  3. The attribute/annotation class in src/ (e.g. Attribute/RouteSecurity.php) — this is the day-to-day ergonomic entry point.
  4. Twig/ — the view-adaptation helpers. This is the bundle's second selling point ("adapt the view according to the logged user") and is where you'll spend most of your time.

First use case — protect one admin route, then hide the nav link:

// src/Controller/Admin/DashboardController.php
use Sil\RouteSecurityBundle\Attribute\RouteSecurity;

#[RouteSecurity(roles: ['ROLE_ADMIN'])]
#[Route('/admin', name: 'admin_dashboard')]
public function dashboard(): Response
{
    return $this->render('admin/dashboard.html.twig');
}
{# templates/base.html.twig #}
{% if is_route_secured('admin_dashboard') is same as(false) or route_security_allows('admin_dashboard') %}
    <a href="{{ path('admin_dashboard') }}">Admin</a>
{% endif %}

Exact helper names must be checked against the Twig extension source — treat the snippet above as the shape of the call, not the literal signature. Get one route enforcing correctly and one link conditionally rendering before wiring the bundle across the app.


Implementation Patterns

Pattern 1: Centralized route map instead of per-controller attributes

For legacy codebases where touching every controller is not feasible, drive the rules from config:

# config/packages/sil_route_security.yaml
sil_route_security:
    routes:
        'admin_*':        ['ROLE_ADMIN']
        'api_internal_*': ['ROLE_SERVICE']
        'report_export':  ['ROLE_MANAGER', 'ROLE_ADMIN']
    default: 'IS_AUTHENTICATED_FULLY'

This scales far better than attributes once you have 100+ routes. Workflow: migrate route-by-route to attributes only when you're already editing that controller; keep the YAML as the source of truth and treat attributes as local overrides (verify which wins — usually config is evaluated first, so an attribute may never fire).

Pattern 2: Keep enforcement in the kernel, adaptation in Twig

The bundle's two halves must not bleed into each other:

  • Enforcement (403 / redirect) belongs in the bundle's listener or a Symfony voter — server-side, non-negotiable.
  • Adaptation (hide button, grey out menu) belongs in Twig — cosmetic only.

The classic anti-pattern is guarding only in the template because "the link is hidden anyway." Anyone with curl disagrees.

Pattern 3: Compose with your firewall and role hierarchy

Don't duplicate security.yaml. Let Symfony own authentication and the hierarchy, and let this bundle own the per-route decision:

# config/packages/security.yaml
security:
    role_hierarchy:
        ROLE_ADMIN: [ROLE_MANAGER, ROLE_USER]
        ROLE_MANAGER: [ROLE_USER]

Then reference leaf roles in route rules. This avoids the situation where adding ROLE_MANAGER requires edits in five config files.

Pattern 4: Voter-backed rules for object-level checks

Role lists handle coarse access. For "can this user see this invoice," define a custom voter and have the route rule delegate to it. This is the natural extension point — the bundle checks, your voter decides.

class InvoiceVoter extends Voter
{
    protected function supports(string $attribute, mixed $subject): bool
    {
        return $attribute === 'VIEW_INVOICE' && $subject instanceof Invoice;
    }

    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
    {
        return $subject->getOwnerId() === $token->getUser()?->getId();
    }
}

Pattern 5: Test the boundary, not the happy path

For every secured route, write a functional test asserting 403 for anonymous, 403 for wrong role, 200 for right role. This is the only thing that catches a typo'd route name in the config — the bundle will happily secure a route that doesn't exist and leave the real one open.

public function testAdminRouteRejectsNonAdmin(): void
{
    $client = static::createClient();
    $client->loginUser($this->createUserWithRoles(['ROLE_USER']));
    $client->request('GET', '/admin');
    self::assertResponseStatusCodeSame(403);
}

Pattern 6: API responses need JSON, not redirects

If any secured route is JSON-only, confirm the bundle's unauthenticated behavior is configurable. HTML routes usually want a redirect to a login page; API routes want 401/403 with a JSON body. If the bundle hardcodes a redirect, wrap it with a request listener that short-circuits /_api/ paths.


Gotchas and Tips

This is your biggest one: 7 stars, 0 dependents, single maintainer. Read the entire src/ directory before adopting — it is likely a few hundred lines. Budget an hour to audit rather than a week to discover the edge cases in production. If the audit reveals behavior you depend on, consider forking or vendoring the single listener class into your own src/Security/ instead of depending on the bundle. The opportunity score (18.3) suggests a real gap in the ecosystem, not a mature solution.

Laravel equivalence mapping — so you know what you're really shopping for:

This bundle (Symfony) Laravel equivalent
Per-route role list in config Route::middleware('role:admin') or can: middleware
RouteSecurity attribute ->middleware('can:view,invoice') on the route
Twig route_security_allows() Blade @can('view', $invoice) / @role('admin')
Symfony voter Laravel Gate / Policy
security.yaml role hierarchy Gate::before() or spatie/laravel-permission role hierarchy
Firewall + access_control auth middleware + AuthServiceProvider

If you're on Laravel, you can build the equivalent in an afternoon with a RouteSecurity middleware and a Blade directive — you do not need this bundle, and there's no Laravel port of it.

Ordering versus the firewall. Symfony resolves the firewall first, then listeners. If the bundle's listener runs before security.firewall populates the token, $token->getUser() is null on every request and every rule denies — or worse, every rule passes. Symptom: everything is 403 even when logged in. Fix: check the listener priority in the bundle's service definition (kernel.event_subscriber tag priority) and confirm it runs after Symfony\Component\Security\Http\Firewall.

Route names, not paths. Security rules keyed on route names silently fail when a route is renamed — there's no compile-time check. The bundle will not warn you that 'admin_dashbaord' (typo) matches nothing. Mitigation: a CI test that iterates $router->getRouteCollection() and asserts every route is either explicitly secured or explicitly in an allowlist. This single test is worth more than the bundle itself.

Clear the cache aggressively. With no Flex recipe, config changes and bundle registration routinely don't take effect until bin/console cache:clear. In dev, if a rule "isn't working," clear the cache before debugging anything else.

Twig helpers are cosmetic, full stop. Never let a route_security_allows() check be the only guard on a state-changing endpoint. Template-level hiding is UX; the listener is security. Assume every hidden form is still submittable.

Anonymous access is the default failure mode. Confirm what happens when a route matches no rule. If the bundle defaults to "allow," you have a fail-open system and every new route is public until someone remembers to secure it. Flip the default to deny and maintain an explicit allowlist — noisy, but you'll sleep.

Role hierarchy may be ignored. Some small bundles do a naive in_array($role, $user->getRoles()) and skip Symfony's RoleHierarchy. That means ROLE_ADMIN does not imply ROLE_USER for these checks, even though the rest of your app assumes it does. Test it explicitly with a user who has only ROLE_ADMIN hitting a ROLE_USER route.

Extension points to look for (and add yourself if absent): a custom voter for object-level rules, a configurable unauthorized response (redirect vs 401 vs 403), and a PSR logger call on denial so denials show up in your logs / Sentry rather than vanishing into a bare 403.

Debugging checklist when a rule misbehaves:

bin/console debug:router admin_dashboard          # is the name real?
bin/console debug:config sil_route_security       # did my config load?
bin/console debug:event-dispatcher kernel.request # is the listener registered, at what priority?
bin/console cache:clear

Then check var/log/dev.log for a denial entry, and confirm the profiler's Security panel shows the token you expect. Nine times out of ten it's a route-name typo or a stale cache — not the bundle.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource