Security Auth - v1.0.0
Extending the Lockout Manager
AccountLockoutManager is concrete (not interface-driven) — bound under AccountLockoutInterface for resolution. To customize behavior, subclass and rebind.
Common extension points
Add notification on lockout
Override lockUser() to notify the user automatically:
namespace App\Auth;
use ArtisanPackUI\SecurityAuth\Authentication\Lockout\AccountLockoutManager as BaseManager;
use ArtisanPackUI\SecurityAuth\Models\AccountLockout;
use Illuminate\Contracts\Auth\Authenticatable;
class NotifyingLockoutManager extends BaseManager
{
public function lockUser(
Authenticatable $user,
string $reason,
string $lockoutType = 'temporary',
?int $durationMinutes = null,
array $metadata = [],
): AccountLockout {
$lockout = parent::lockUser( $user, $reason, $lockoutType, $durationMinutes, $metadata );
$user->notify( new \App\Notifications\AccountLocked( $lockout ) );
return $lockout;
}
}
Register your subclass:
$this->app->singleton( \ArtisanPackUI\SecurityAuth\Authentication\Lockout\AccountLockoutManager::class, NotifyingLockoutManager::class );
$this->app->bind( \ArtisanPackUI\SecurityAuth\Authentication\Contracts\AccountLockoutInterface::class, NotifyingLockoutManager::class );
Tier-based lockout durations
Vary lockout duration by user tier (premium users get shorter lockouts, etc.):
class TieredLockoutManager extends BaseManager
{
public function lockUser(
Authenticatable $user,
string $reason,
string $lockoutType = 'temporary',
?int $durationMinutes = null,
array $metadata = [],
): AccountLockout {
$durationMinutes ??= match ( $user->tier ?? 'free' ) {
'enterprise' => 5,
'premium' => 10,
default => 30,
};
return parent::lockUser( $user, $reason, $lockoutType, $durationMinutes, $metadata );
}
}
Skip lockout for trusted IPs
Override recordFailedAttempt() to bypass for office IPs:
class TrustedIpLockoutManager extends BaseManager
{
public function recordFailedAttempt(
string $trigger,
?Authenticatable $user = null,
?string $ipAddress = null,
): array {
if ( in_array( $ipAddress, config('security.trusted_ips', []), true ) ) {
return ['locked' => false, 'attempts' => 0, 'remaining' => PHP_INT_MAX];
}
return parent::recordFailedAttempt( $trigger, $user, $ipAddress );
}
}
Conventions
- Preserve event firing. The base
lockUser()dispatchesAccountLocked. Callingparent::lockUser()(rather than reimplementing) keeps the event flowing to any listeners. - Return types must match. The contract returns
AccountLockoutfrom lock methods; your overrides must too. - Keep manager state minimal. The base class is mostly stateless — derived state lives in the DB rows. Your overrides should match that pattern.