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

Security Bundle Laravel Package

symfony/security-bundle

SecurityBundle tightly integrates Symfony's Security component into the full-stack framework, giving you configuration-driven authentication, authorization, firewalls, access control and user providers without wiring the services by hand.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require symfony/security-bundle
    

    Add to config/bundles.php:

    return [
        // ...
        Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
    ];
    
  2. Basic Configuration (config/packages/security.yaml):

    security:
        enable_authenticator_manager: true
        password_hashers:
            Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
        firewalls:
            main:
                lazy: true
                provider: app_user_provider
                form_login: ~
        providers:
            app_user_provider:
                entity:
                    class: App\Entity\User
                    property: email
    
  3. First Use Case:

    • Create a User entity with UserInterface and PasswordAuthenticatedUserInterface.
    • Secure a route with annotations:
      use Symfony\Component\Security\Http\Attribute\IsGranted;
      
      #[IsGranted('ROLE_USER')]
      public function secureAction(): Response
      {
          return new Response('Secure content');
      }
      

Key Files to Review

  • config/packages/security.yaml: Core configuration.
  • src/Security/LoginFormAuthenticator.php: Custom authenticator example.
  • src/Entity/User.php: User entity with roles/credentials.

Before You Start: Pick Your Branch

  • The current work is published as v8.1.0-BETA3, while the shipped security/bugfix changelog covers the 5.4.x maintenance line (latest: v5.4.54, a [SECURITY] release). If you are on 5.4 LTS, pin symfony/security-bundle:^5.4.54 — the patch is a drop-in upgrade, no config changes required.
  • If you are on 7.x/8.x, the same fixes are already contained in your branch; treat 8.1.0-BETA3 as feature work and 5.4.54 as the minimum safe patch level for legacy apps.

Implementation Patterns

1. Authentication Workflows

Form Login

# config/packages/security.yaml
firewalls:
    main:
        form_login:
            login_path: app_login
            check_path: app_login
            enable_csrf: true
  • Create a controller for app_login with AuthenticationUtils to handle login errors:
    use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
    
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        $error = $authenticationUtils->getLastAuthenticationError();
        $lastUsername = $authenticationUtils->getLastUsername();
        return $this->render('security/login.html.twig', [
            'error' => $error,
            'last_username' => $lastUsername,
        ]);
    }
    

Login Throttling (now lock-backed by default)

Changed in v5.4.54: login throttling uses a lock by default instead of cache-based counters.

# config/packages/security.yaml
firewalls:
    main:
        login_throttling:
            max_attempts: 5
            interval: '15 minutes'
            # lock_factory: lock.default.factory  # optional override
  • On a single host the default flock-style lock is fine.
  • On multiple app servers, configure a shared lock store, otherwise each node throttles independently:
    # config/packages/lock.yaml
    framework:
        lock:
            default: '%env(LOCK_DSN)%'   # redis://..., semaphore://..., etc.
            login_throttling: '%env(LOCK_DSN)%'
    
  • Requires a working lock component; in restricted containers (read-only FS, no /tmp), point the lock at a writable store or it will fail at login time.

Custom Authenticators

Extend AbstractAuthenticator for OAuth, API tokens, or custom logic:

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;

class CustomAuthenticator extends AbstractAuthenticator
{
    public function supports(Request $request): ?bool
    {
        return $request->headers->has('X-API-TOKEN');
    }

    public function authenticate(Request $request): Passport
    {
        $token = $request->headers->get('X-API-TOKEN');
        return new Passport(new ApiKeyAuthenticator($token));
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return new RedirectResponse('/dashboard');
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        return new JsonResponse(['error' => $exception->getMessage()], 401);
    }
}

Register in security.yaml:

firewalls:
    api:
        pattern: ^/api
        stateless: true
        custom_authenticator: app.custom_authenticator

2. Authorization Patterns

Role-Based Access Control (RBAC)

# config/packages/security.yaml
access_control:
    - { path: ^/admin, roles: ROLE_ADMIN }
    - { path: ^/profile, roles: ROLE_USER }
  • Use in controllers:
    #[IsGranted('ROLE_ADMIN')]
    public function adminDashboard(): Response
    {
        return new Response('Admin Panel');
    }
    

Voter System

Create a voter for complex logic (e.g., "can edit own profile"):

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;

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

    protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
    {
        $user = $token->getUser();
        return $user === $subject || $user->hasRole('ROLE_ADMIN');
    }
}

Register in security.yaml:

security:
    access_decision_manager:
        strategy: affirmative
    voters:
        App\Security\ProfileVoter: ~

3. Session and Firewalls

Lazy Firewalls

firewalls:
    main:
        lazy: true  # Loads only when accessed
        provider: app_user_provider
  • Useful for performance (e.g., admin panels rarely visited).

Multiple Firewalls

firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    api:
        pattern: ^/api
        stateless: true
        jwt: ~
    main:
        pattern: ^/
        form_login: ~

Stateless / Session-less Redirect Behaviour

Changed in v5.4.54: redirections are now restricted to the current host when sessions are disabled.

If you run stateless firewalls (API tokens, JWT) that redirect after login/registration, cross-host targets are now dropped. Keep redirect targets host-relative (or build them with UrlGeneratorInterface::ABSOLUTE_PATH) rather than echoing a user-supplied redirect parameter.

// Safe: stays on the current host
return new RedirectResponse($this->generateUrl('dashboard'));

// Previously worked on stateless firewalls, now blocked by design
// return new RedirectResponse('https://other.example.com/...');

4. Integration with Other Bundles

API Platform + JWT

# config/packages/security.yaml
security:
    firewalls:
        api:
            pattern: ^/api
            stateless: true
            jwt: ~
    providers:
        api_user_provider:
            entity:
                class: App\Entity\User
                property: apiToken
  • Use lexik/jwt-authentication-bundle for JWT generation/validation.

OAuth (e.g., Google)

# config/packages/security.yaml
security:
    firewalls:
        main:
            oauth:
                resource_owners:
                    google: ~
                login_path: /connect/google
                use_forward: false
                failure_path: /login
  • Review redirect URIs against the tightened host restriction above if you use use_referer or a post-login target_path that crosses hosts.

Gotchas and Tips

1. Common Pitfalls

CSRF Token Mismatch

  • Issue: Invalid CSRF token on form submission.
  • Fix: Ensure enable_csrf: true in form_login and the form includes {{ form_row(form._token) }}.
  • Debug: Check php bin/console debug:firewall for active firewalls.

Lazy Firewall + Remember-Me

  • Issue: Remember-me tokens trigger auth on public routes (e.g., POST to /contact).
  • Fix: Explicitly exclude routes or use lazy: true carefully:
    firewalls:
        main:
            lazy: true
            remember_me:
                secret: '%kernel.secret%'
                lifetime: 86400
                path: /login
    

Role Hierarchy Confusion

  • Issue: ROLE_ADMIN not inheriting ROLE_USER permissions.
  • Fix: Define hierarchy in security.yaml:
    security:
        role_hierarchy:
            ROLE_ADMIN: ROLE_USER
            ROLE_USER: ROLE_GUEST
    

Login Throttling Breaks in Containers / Multi-Node Setups

  • Issue: After v5.4.54, throttling relies on a lock rather than cache. Read-only filesystems, ephemeral containers, or N app servers silently defeat it (or throw on lock acquisition).
  • Fix: Configure framework.lock with a shared backend (Redis, PDO, etc.) and verify with a login failure loop in staging.
  • Related: If your tests assert exact "too many attempts" messages, they may now fire earlier/later — assert on the HTTP status (429), not the message text.

2. Debugging Tips

Profiler Toolbar

  • Use the Security tab to inspect:
    • Current user/token.
    • Firewall matches.
    • Access control decisions.

CLI Commands

  • Dump role hierarchy:
    php bin/console debug:security:role-hierarchy
    
  • Check firewalls:
    php bin/console debug:firewall
    
  • Test voters:
    php bin/console debug:voter App\Entity\Post 1 EDIT
    

Logging

Enable debug logging in config/packages/monolog.yaml:

monolog:
    handlers:
        security:
            type: stream
            path: "%kernel.logs_dir%/%kernel.environment%.security.log"
            level: debug
            channels: ["security"]

3. Configuration Quirks

Deprecated Features

  • Removed: hide_user_not_found (Symfony 8+).
    • Fix: Use an authentication_failure_path / custom AuthenticationFailureHandler.
  • Removed: XML configuration (Symfony 7.4+).
    • Fix: Migrate to YAML/PHP.

OIDC/JWT Caveats

  • Trusted Hosts: Required for CAS/OIDC setups.
    security:
        oidc:
            trusted_hosts:
                - example.com
    
  • JWKS Cache: Clear and warm it after upgrading on the 5.4 line:
    php bin/console cache:pool:clear cache.security_oauth2
    

Upgrade Checklist for v5.4.54

Area What to check
Stateless firewalls Any redirect to an absolute cross-host URL is now blocked when stateless: true / sessions disabled.
Login throttling framework.lock is reachable and shared across nodes.
Tests Throttling + redirect assertions; run your auth feature tests after the bump.
Composer composer update symfony/security-bundle --with-dependencies and re-run the firewall debug command.

4. Extension Points

Custom User Providers

# config/packages/security.yaml
providers:
    custom_provider:
        id: App\Security\CustomUserProvider
  • Implement UserProviderInterface:
    class CustomUserProvider implements UserProviderInterface
    {
        public function loadUserByIdentifier(string $identifier): UserInterface
        {
            // ...
        }
    
        public function refreshUser(UserInterface $user): UserInterface
        {
            // ...
        }
    
        public function supportsClass(string $class): bool
        {
            return $userClass === $class || is_subclass_of($class, $userClass);
        }
    }
    
  • Tip: supportsClass() must return true for subclasses or you get "user not refreshed" errors on session reload — a frequent false lead when debugging unexpected logouts.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core