Weave Code
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 Core Laravel Package

symfony/security-core

Symfony Security Core provides the core building blocks for authentication and authorization: tokens, voters, role hierarchies, access decision management, and user providers. Use it to implement flexible permission checks and separate security logic from user storage.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup in Laravel

  1. Installation

    composer require symfony/security-core
    

    Laravel already bundles Symfony’s Security component, but this package provides the core classes for custom implementations.

  2. First Use Case: Role-Based Access Control (RBAC)

    • Extend Laravel’s built-in auth by creating a custom AccessDecisionManager for granular permissions.
    • Example: Override Laravel’s AuthenticatesUsers trait to integrate Symfony’s voters.
  3. Key Classes to Explore

    • AccessDecisionManager: Orchestrates authorization logic.
    • RoleHierarchy: Manages role inheritance (e.g., ROLE_ADMINROLE_USER).
    • Voter (e.g., RoleVoter, AuthenticatedVoter): Customize decision-making logic.
  4. Where to Look First

    • Symfony Security Docs (for theory).
    • Laravel’s app/Http/Middleware/Authenticate.php (to see how tokens are resolved).
    • vendor/symfony/security-core/Authorization/Voter/ (for voter implementations).

Implementation Patterns

1. Integrating with Laravel’s Auth System

  • Token Resolution: Use AuthenticationTrustResolver to check if a token is fully authenticated (e.g., after login).

    use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
    
    $trustResolver = new AuthenticationTrustResolver();
    if (!$trustResolver->isAuthenticated($token)) {
        // Redirect to login or throw exception.
    }
    
  • Custom Voters: Create a voter for domain-specific logic (e.g., CanEditPostVoter).

    use Symfony\Component\Security\Core\Authorization\Voter\Voter;
    
    class CanEditPostVoter extends Voter {
        protected function supports(string $attribute, mixed $subject): bool {
            return $attribute === 'EDIT_POST' && $subject instanceof Post;
        }
    
        protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool {
            return $subject->author->id === $token->getUser()->id;
        }
    }
    
  • AccessDecisionManager Workflow: Combine voters in a decision manager to evaluate multiple rules.

    $decisionManager = new AccessDecisionManager([
        new AuthenticatedVoter($trustResolver),
        new CanEditPostVoter(),
        new RoleVoter(),
    ]);
    
    if (!$decisionManager->decide($token, ['EDIT_POST'], $post)) {
        abort(403);
    }
    

2. Role Hierarchy for DRY Permissions

  • Define role inheritance in a RoleHierarchy instance.
    $roleHierarchy = new RoleHierarchy([
        'ROLE_SUPER_ADMIN' => ['ROLE_ADMIN', 'ROLE_AUDITOR'],
        'ROLE_ADMIN' => ['ROLE_USER'],
    ]);
    
  • Pass it to RoleHierarchyVoter to automatically grant inherited roles.

3. Middleware Integration

  • Create a middleware to wrap AccessDecisionManager checks:
    namespace App\Http\Middleware;
    
    use Closure;
    use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
    
    class AuthorizeMiddleware {
        public function __construct(private AccessDecisionManager $decisionManager) {}
    
        public function handle($request, Closure $next, string $permission) {
            if (!$this->decisionManager->decide(
                $request->user()->getToken(),
                [$permission],
                $request->route()->parameter('model')
            )) {
                abort(403);
            }
            return $next($request);
        }
    }
    
  • Register in app/Http/Kernel.php:
    protected $routeMiddleware = [
        'authorize' => \App\Http\Middleware\AuthorizeMiddleware::class,
    ];
    
    Usage in routes:
    Route::get('/admin/posts/{post}', function (Post $post) {
        // ...
    })->middleware(['auth', 'authorize:EDIT_POST']);
    

4. Testing Authorization Logic

  • Mock AccessDecisionManager and voters in PHPUnit:
    $token = $this->createMock(TokenInterface::class);
    $token->method('getUser')->willReturn($user);
    
    $decisionManager = $this->createMock(AccessDecisionManager::class);
    $decisionManager->method('decide')->willReturn(true);
    
    $this->app->instance(AccessDecisionManager::class, $decisionManager);
    

Gotchas and Tips

Pitfalls

  1. Token Mismatch:

    • Ensure the TokenInterface passed to decide() matches the one resolved by Laravel’s auth system.
    • Symfony’s UsernamePasswordToken is rarely used directly in Laravel; prefer Laravel\Sanctum\PersonalAccessToken or Illuminate\Auth\SessionGuard tokens.
  2. Circular Role Dependencies:

    • RoleHierarchy throws CircularReferenceException if roles reference each other (e.g., A → B → A).
    • Validate hierarchy with:
      $roleHierarchy->getReachableRoleNames('ROLE_ADMIN'); // Test reachability.
      
  3. Voter Order Matters:

    • Voters are evaluated in order until one denies access. Place stricter voters (e.g., CanEditPostVoter) before RoleVoter.
  4. Laravel’s Gate vs. Symfony’s Voters:

    • Avoid mixing Gate::forUser() with AccessDecisionManager in the same flow—it can lead to inconsistent results.
    • Use one system per feature (e.g., Gates for simple checks, Voters for complex RBAC).

Debugging

  • Log Decisions: Extend AccessDecisionManager to log voter results:

    class DebugAccessDecisionManager extends AccessDecisionManager {
        public function decide(TokenInterface $token, array $attributes, $subject = null): bool {
            $result = parent::decide($token, $attributes, $subject);
            \Log::debug('Authorization decision:', [
                'attributes' => $attributes,
                'subject' => method_exists($subject, 'getId') ? $subject->getId() : $subject,
                'result' => $result,
            ]);
            return $result;
        }
    }
    
  • Inspect Token: Dump the token to verify roles/credentials:

    dd($request->user()->getToken()->getRoles());
    

Extension Points

  1. Custom Attributes: Replace string attributes (e.g., 'EDIT_POST') with domain objects:

    class PostPermission {}
    $decisionManager->decide($token, [new PostPermission()], $post);
    
  2. Dynamic Voters: Load voters from service providers or config:

    $voters = collect(config('security.voters'))
        ->map(fn ($class) => new $class())
        ->toArray();
    
  3. Attribute Mappers: Convert Laravel’s Gates to Symfony attributes:

    $attributeMapper = new AttributeMapper([
        'edit-post' => 'EDIT_POST',
        'delete-own' => 'DELETE_OWN_RESOURCE',
    ]);
    $symfonyAttribute = $attributeMapper->map('edit-post');
    

Config Quirks

  • Role Prefixes: Symfony expects roles to start with ROLE_ (e.g., ROLE_ADMIN). Laravel’s Gate::allows() uses raw strings. Normalize roles when bridging:

    $symfonyRole = 'ROLE_' . strtoupper(str_replace('-', '_', $laravelGate));
    
  • Token Storage: Symfony’s TokenStorage interface differs from Laravel’s AuthManager. Use a bridge:

    use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
    
    class LaravelTokenStorage implements TokenStorageInterface {
        public function __construct(private \Illuminate\Contracts\Auth\Authenticatable $user) {}
    
        public function getToken(): ?TokenInterface {
            return new UsernamePasswordToken($this->user, 'laravel', $this->user->getRoles());
        }
    }
    

Performance Tips

  • Cache Role Hierarchy: RoleHierarchy is thread-safe; instantiate it once and reuse:
    $this->app->singleton(RoleHierarchy::class, fn () => new RoleHierarchy($roles));
    
  • Avoid Redundant Voters: Combine logic into fewer voters (e.g., merge AuthenticatedVoter and RoleVoter if roles are only checked for authenticated users).
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.
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui
babelqueue/php-sdk
facebook/capi-param-builder-php
babelqueue/symfony
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle