raditzfarhan/laravel-user-security
Installation
composer require raditzfarhan/laravel-user-security:^1.0
For Laravel ≥5.5, auto-discovery handles the service provider registration. For older versions or Lumen, manually register RaditzFarhan\UserSecurity\UserSecurityServiceProvider in config/app.php or bootstrap/app.php.
Publish Config Publish the default config to customize security settings:
php artisan vendor:publish --provider="RaditzFarhan\UserSecurity\UserSecurityServiceProvider" --tag="config"
Modify config/user-security.php to adjust:
pin_length (default: 6)mnemonic_length (default: 12)two_factor_enabled (default: false)Extend User Model
Add the HasSecurity trait to your User model:
use RaditzFarhan\UserSecurity\Traits\HasSecurity;
class User extends Authenticatable
{
use HasSecurity;
// ...
}
Run migrations:
php artisan migrate
First Use Case: Enforcing a Security PIN
// Set a PIN for a user
$user->setSecurityPin('123456');
// Verify PIN during login
if (RFAuthenticator::verifyPin($user, '123456')) {
// Proceed with authentication
}
// Generate and store a PIN (auto-generated or user-provided)
$pin = RFAuthenticator::generatePin(); // Default: 6-digit
$user->setSecurityPin($pin);
// Middleware example: `app/Http/Middleware/CheckPin.php`
public function handle($request, Closure $next)
{
$user = auth()->user();
if (!$user->hasSecurityPin() || !RFAuthenticator::verifyPin($user, $request->pin)) {
return redirect()->back()->withErrors(['pin' => 'Invalid PIN']);
}
return $next($request);
}
// Reset via email (extend with Laravel Notifications)
$user->resetSecurityPin();
$mnemonic = RFAuthenticator::generateMnemonic(); // Default: 12-word phrase
$user->setMnemonicKey($mnemonic);
if (RFAuthenticator::verifyMnemonic($user, $request->mnemonic)) {
// Unlock account or reset PIN
}
// Show mnemonic in a secure modal (e.g., during registration)
<div class="mnemonic-display">{{ $user->mnemonic_key }}</div>
$user->enableTwoFactorAuth();
$secret = RFAuthenticator::generateTwoFactorSecret();
$user->setTwoFactorSecret($secret);
use RaditzFarhan\UserSecurity\Facades\RFAuthenticator;
if (RFAuthenticator::verifyTwoFactorCode($user, $request->code)) {
// Authenticate
}
qrlink package):
$qrCodeUrl = RFAuthenticator::getTwoFactorQRCodeUrl($user);
Route::middleware(['auth', 'pin'])->group(function () {
// Routes requiring PIN
});
Register middleware in app/Http/Kernel.php:
protected $routeMiddleware = [
'pin' => \RaditzFarhan\UserSecurity\Http\Middleware\CheckPin::class,
'mnemonic' => \RaditzFarhan\UserSecurity\Http\Middleware\CheckMnemonic::class,
'twofactor' => \RaditzFarhan\UserSecurity\Http\Middleware\CheckTwoFactor::class,
];
public function login(Request $request)
{
$credentials = $request->only(['email', 'password', 'pin']);
if (!RFAuthenticator::verifyPin($request->user(), $credentials['pin'])) {
return response()->json(['error' => 'Invalid PIN'], 401);
}
// Proceed with login
}
// In User model
public function setSecurityPin($pin = null)
{
$pin = $pin ?? $this->generateCustomPin();
parent::setSecurityPin($pin);
}
protected function generateCustomPin()
{
return Str::random(6); // Alphanumeric PIN
}
// Encrypt mnemonic before saving
public function setMnemonicKeyAttribute($value)
{
$this->attributes['mnemonic_key'] = encrypt($value);
}
// In EventServiceProvider
protected $listen = [
\RaditzFarhan\UserSecurity\Events\PinVerified::class => [
\App\Listeners\LogSecurityEvent::class,
],
];
public function test_pin_validation()
{
$user = User::factory()->create();
$user->setSecurityPin('123456');
$this->assertTrue(RFAuthenticator::verifyPin($user, '123456'));
$this->assertFalse(RFAuthenticator::verifyPin($user, '000000'));
}
Migration Conflicts
security_pin, mnemonic_key, two_factor_secret, and two_factor_recovery_codes columns to the users table. If your schema already has these columns, manually resolve conflicts or rename existing columns to avoid overwrites.PIN Brute Force
// app/Http/Middleware/ThrottlePinAttempts.php
public function handle($request, Closure $next)
{
$key = $request->ip() . '|' . $request->user()->id;
if (request()->attempts($key, 5)) {
return response()->json(['error' => 'Too many attempts'], 429);
}
return $next($request);
}
Mnemonic Key Exposure
setMnemonicKeyAttribute).2FA Secret Management
two_factor_secret is base32-encoded but stored as plaintext. For production:
encrypt() to store the secret.Lumen Facades
$app->withFacades(true) will cause RFAuthenticator:: calls to fail with Class not found errors.Session Binding
auth middleware to access the authenticated user:
// Correct order in Kernel.php
'auth' => \App\Http\Middleware\Authenticate::class,
'pin' => \RaditzFarhan\UserSecurity\Http\Middleware\CheckPin::class,
Verify Config
config/user-security.php for misconfigured values (e.g., pin_length set to 0).Check Database
users table has the expected columns:
SELECT column_name FROM information_schema.columns
WHERE table_name = 'users' AND column_name LIKE '%security%';
Log Facade Calls
// In RFAuthenticator facade
public static function verifyPin
How can I help you explore Laravel packages today?