metaclass-nl/authentication-guard-bundle
Symfony bundle for integrating guard-based authentication into your application. It helps wire custom authentication guards into Symfony Security so you can protect routes and control how users are authenticated, with straightforward configuration and reusable guard support.
First reality check: metaclass-nl/authentication-guard-bundle is a Symfony 2 bundle, not a Laravel package. It will not drop into a Laravel app, and there is no Laravel equivalent release. It also has no releases since 2017 and is archived — treat it as a reference implementation, not a dependency.
What it does (the part worth your time): it protects a login flow against brute force and dictionary attacks by tracking failed authentication attempts and applying guard/lockout logic. That is the valuable asset here.
Minimal steps if you were on Symfony 2 (for archaeology / evaluation):
composer require metaclass-nl/authentication-guard-bundle — expect constraints pinned to old Symfony 2 PHP versions; you will likely need an old PHP runtime.app/AppKernel.php:
public function registerBundles()
{
$bundles = [
// ...
new Metaclass\AuthenticationGuardBundle\MetaclassAuthenticationGuardBundle(),
];
return $bundles;
}
app/config/config.yml and wire the guard to your authentication provider. Read the README first for the exact config keys — they are not stable documentation and the repo is the only source of truth.First use case to evaluate: "lock an account or IP after X failed logins within Y minutes." Confirm that behavior works in the bundle before investing in a port.
For a Laravel developer, "Getting Started" really means: clone the repo, read Resources/config/ and the guard classes, and use it as a spec for what you will build natively (see below).
The Laravel-native equivalent of this bundle is a combination of rate limiting and the Illuminate\Auth\Events hooks. This is the pattern to reach for day-to-day:
// Register a login attempt tracker
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('login', function (Request $request) {
$key = Str::lower($request->input('email')).'|'.$request->ip();
return Limit::perMinute(5)->by($key);
});
Then in your LoginRequest or controller:
if (RateLimiter::tooManyAttempts($key, 5)) {
throw ValidationException::withMessages([
'email' => 'Too many attempts. Try again in '.RateLimiter::availableIn($key).' seconds.',
]);
}
On success: RateLimiter::clear($key). On failure: RateLimiter::hit($key, 900).
The bundle's structure (a guard watching authentication) maps cleanly onto Laravel events:
// EventServiceProvider
protected $listen = [
\Illuminate\Auth\Events\Failed::class => [
\App\Listeners\RecordFailedLogin::class,
],
\Illuminate\Auth\Events\Lockout::class => [
\App\Listeners\NotifyOfLockout::class,
],
];
In RecordFailedLogin, persist attempts to a table keyed by (identifier, ip) so you can implement dictionary-attack detection (many distinct passwords, same email) rather than just simple counting. That distinction is what this bundle was actually pitched at, and simple RateLimiter alone won't catch a distributed dictionary attack.
Wrap protected routes in a middleware that checks an "is this identity locked" store before the auth attempt, rather than after:
class EnsureNotLocked
{
public function handle($request, Closure $next)
{
$key = Str::lower($request->input('email')).'|'.$request->ip();
abort_if(RateLimiter::tooManyAttempts($key, 5), 429);
return $next($request);
}
}
Only in a legacy Symfony 2 app you already maintain. Treat it as one service in the container: register the bundle, point your security.yml firewall's guard/listener at it, and keep it isolated behind a firewall config block so you can swap it later. Do not spread its classes through application code — the archived status means you will replace it.
Resources/config/.RateLimiter default) over request-scoped state so limits survive workers/restarts.RateLimiter::attempts($key), check your cache driver is shared across processes, and confirm Failed/Lockout listeners are actually registered — silent no-ops are the usual cause of "protection not working."How can I help you explore Laravel packages today?