Installation:
composer require atoolo/security-bundle
Ensure your config/bundles.php includes the bundle:
return [
// ...
Atoolo\SecurityBundle\AtooloSecurityBundle::class => ['all' => true],
];
Configuration:
Copy the default security configuration from the bundle's Resources/config/security.yaml to your project's config/packages/security.yaml. Key sections to review:
providers (user loading strategy)firewalls (entry points, authentication methods)role_hierarchy (role inheritance)access_control (route-based permissions)First Use Case: Secure a route using role-based access:
# config/packages/security.yaml
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
Then annotate your controller:
use Symfony\Component\Security\Http\Attribute\IsGranted;
class AdminController {
#[IsGranted('ROLE_ADMIN')]
public function dashboard(): Response { ... }
}
User Management:
The bundle integrates with IES CMS for role/user management. Use the UserProfile interface to extend user data:
use Atoolo\SecurityBundle\Model\UserProfile;
class CustomUser implements UserProfile {
// Implement required methods
}
Form Login:
Configure in security.yaml:
firewalls:
main:
form_login:
login_path: app_login
check_path: app_login_check
default_target_path: dashboard
Create a login controller:
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class LoginController {
public function login(AuthenticationUtils $authenticationUtils): Response {
$error = $authenticationUtils->getLastAuthenticationError();
return $this->render('security/login.html.twig', ['error' => $error]);
}
}
JWT Authentication (via LexikBundle):
Enable in security.yaml:
firewalls:
api:
pattern: ^/api
stateless: true
jwt: ~
Dynamic Roles:
Use the role_hierarchy to define inheritance:
role_hierarchy:
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
ROLE_ADMIN: ROLE_USER
Assign roles via IES CMS or programmatically:
$user->addRole('ROLE_EDITOR');
$user->setRoles(['ROLE_USER', 'ROLE_EDITOR']); // Overwrites existing roles
Custom Role Voter: Extend the bundle's voter system:
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
class CustomRoleVoter implements VoterInterface {
public function supports(string $attribute, $subject): bool { ... }
public function vote(..., string $attribute, mixed $subject): int { ... }
}
Register as a service with the security.voter tag.
security.yaml:
providers:
app_user_provider:
entity: { class: App\Entity\User, property: email }
Use the UserProfile interface to add CMS-managed fields:
class User implements UserInterface, UserProfile {
public function getProfileField(string $fieldName): ?string { ... }
public function setProfileField(string $fieldName, ?string $value): void { ... }
}
PasswordUpdater service:
use Atoolo\SecurityBundle\Service\PasswordUpdater;
$updater = $this->container->get(PasswordUpdater::class);
$updater->updatePassword($user, 'newPassword123!');
CanonicalHostService to enforce host-based access:
use Atoolo\SecurityBundle\Service\CanonicalHostService;
$hostService = $this->container->get(CanonicalHostService::class);
if (!$hostService->isCanonicalHost($request->getHost())) {
throw new AccessDeniedException('Invalid host');
}
Service Container:
Bind the bundle's services to Laravel's container in AppServiceProvider:
public function register(): void {
$this->app->bind(
Atoolo\SecurityBundle\Service\PasswordUpdater::class,
fn($app) => new \Atoolo\SecurityBundle\Service\PasswordUpdater(
$app->make('security.password_hasher'),
$app->make('security.user_checker')
)
);
}
Middleware: Convert Symfony's security middleware to Laravel:
// app/Http/Middleware/Authenticate.php
use Atoolo\SecurityBundle\Security\Authentication\AuthenticationManager;
public function handle(Request $request, Closure $next) {
$authManager = app(AuthenticationManager::class);
$token = $authManager->authenticate($request);
if ($token) {
auth()->setUser($token->getUser());
}
return $next($request);
}
Routing: Use Laravel's route middleware for access control:
Route::get('/admin', function () {
// ...
})->middleware(['auth', 'role:admin']);
Events: Listen to Symfony's security events via Laravel's event system:
Event::listen(SecurityEvents::AUTHENTICATION_SUCCESS, function ($event) {
// Convert Symfony event to Laravel
auth()->login($event->getAuthenticationToken()->getUser());
});
IES User Sync:
Use the UserProfile interface to sync CMS-managed fields:
class User implements UserInterface, UserProfile {
public function getProfileField(string $fieldName): ?string {
return $this->iesProfileData[$fieldName] ?? null;
}
public function setProfileField(string $fieldName, ?string $value): void {
$this->iesProfileData[$fieldName] = $value;
$this->save(); // Persist to DB
}
}
Role Sync: Implement a cron job to sync roles from IES:
use Atoolo\SecurityBundle\Service\RoleManager;
$roleManager = app(RoleManager::class);
$roleManager->syncRolesFromCMS();
Symfony vs. Laravel Quirks:
security.user_checker). In Laravel, bind these explicitly to avoid ServiceNotFoundException.EventDispatcher is not Laravel's Dispatcher. Use the SymfonyEventDispatcher facade or create a bridge:
$dispatcher = new SymfonyEventDispatcher(app('events'));
Password Hashing:
PasswordHasherInterface. In Laravel, use:
$hasher = app(\Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface::class);
getSalt() is removed in Symfony 6+. Use getSalt() from the user object or rely on Symfony's auto-salt generation.Role Hierarchy:
ROLE_SUPER ADMIN) may cause issues. Trim roles in a voter:
$role = trim($role);
Canonical Host:
CanonicalHostService requires a configured canonical_host in security.yaml. If missing, it defaults to null, bypassing checks.JWT Integration:
user_provider key in security.yaml. Ensure it matches your Laravel user provider:
lexik_jwt: ~
providers:
lexik_jwt:
entity: { class: App\Models\User, property: email }
User Provider Conflicts:
Authenticatable, ensure the UserInterface methods (getRoles(), eraseCredentials()) are implemented. The bundle expects Symfony's UserInterface.$this->app->make('debug')->setDebug(true);
How can I help you explore Laravel packages today?