laraditz/user-security
Adds user security features for Laravel/Lumen: security PIN, mnemonic key validation/storage, and 2FA support. Includes a UserSecurable trait, SecureUser facade, and configurable hashing key (LUS_KEY) for one-way encryption.
Installation
composer require laraditz/user-security
For Laravel ≥5.5, auto-discovery handles the service provider registration. Otherwise, add Laraditz\UserSecurity\UserSecurityServiceProvider::class to config/app.php.
Publish Config Publish the default configuration:
php artisan vendor:publish --provider="Laraditz\UserSecurity\UserSecurityServiceProvider" --tag="config"
This generates config/user-security.php with default settings (e.g., pin length, 2FA algorithm).
Run Migrations
Publish and run migrations to add security fields to the users table:
php artisan vendor:publish --provider="Laraditz\UserSecurity\UserSecurityServiceProvider" --tag="migrations"
php artisan migrate
This adds columns: security_pin, mnemonic_key, two_factor_secret, and two_factor_recovery_codes.
First Use Case: Enabling a Security Pin Assign a pin to a user via Tinker or a controller:
use Laraditz\UserSecurity\Facades\UserSecurity;
$user = \App\Models\User::find(1);
UserSecurity::setSecurityPin($user, '1234'); // Default length: 4
UserSecurity::setSecurityPin($user, $pin);
UserSecurity::updateSecurityPin($user, $oldPin, $newPin); // For validation
if (UserSecurity::validateSecurityPin($user, $pin)) {
// Proceed with secure action
}
AuthenticatesUsers trait to require a pin post-login:
public function postLogin(Request $request)
{
$this->authenticate($request);
$user = $this->guard()->user();
if (!$request->has('security_pin') || !UserSecurity::validateSecurityPin($user, $request->security_pin)) {
return back()->withErrors(['pin' => 'Invalid pin.']);
}
return redirect()->intended();
}
$mnemonic = UserSecurity::generateMnemonicKey($user);
// Store $mnemonic securely (e.g., encrypted in DB or session)
if (UserSecurity::validateMnemonicKey($user, $providedMnemonic)) {
// Grant access to recovery flow
}
Route::post('/recover-account', function (Request $request) {
$user = User::where('email', $request->email)->first();
if ($user && UserSecurity::validateMnemonicKey($user, $request->mnemonic)) {
Auth::login($user);
return redirect()->route('dashboard');
}
return back()->withErrors(['mnemonic' => 'Invalid key.']);
});
$secret = UserSecurity::enableTwoFactorAuth($user);
// Display QR code or manual entry key (e.g., using `laravel-2fa` or similar)
if (UserSecurity::validateTwoFactorToken($user, $token)) {
// Grant access
}
laravel-2fa for UI/QR generation:
use Laraditz\UserSecurity\Facades\UserSecurity;
use LaravelTwoFactorAuth\TwoFactorAuth;
$secret = UserSecurity::enableTwoFactorAuth($user);
$twoFactor = app(TwoFactorAuth::class);
$qrCodeUrl = $twoFactor->getQRCodeUrl($user->email, $secret);
$codes = UserSecurity::generateTwoFactorRecoveryCodes($user, 10); // 10 codes
UserSecurity::revokeTwoFactorRecoveryCodes($user);
$admins = User::where('role', 'admin')->get();
foreach ($admins as $admin) {
UserSecurity::setSecurityPin($admin, str_pad(rand(0, 9999), 4, '0', STR_PAD_LEFT));
}
Create middleware to enforce security checks:
namespace App\Http\Middleware;
use Closure;
use Laraditz\UserSecurity\Facades\UserSecurity;
class RequireSecurityPin
{
public function handle($request, Closure $next)
{
if (!UserSecurity::validateSecurityPin($request->user(), $request->pin)) {
abort(403, 'Invalid security pin.');
}
return $next($request);
}
}
Register in app/Http/Kernel.php:
protected $routeMiddleware = [
'pin.required' => \App\Http\Middleware\RequireSecurityPin::class,
];
Usage:
Route::get('/secure-area', function () {
// ...
})->middleware('auth:web', 'pin.required');
Extend core events (e.g., registered, password.resetting) to auto-generate security features:
// In EventServiceProvider
protected $listen = [
'Illuminate\Auth\Events\Registered' => [
'App\Listeners\GenerateSecurityPinOnRegistration',
],
];
Listener:
public function handle($event)
{
UserSecurity::setSecurityPin($event->user, str_pad(rand(0, 9999), 4, '0'));
}
For APIs, return security status in user profiles:
Route::get('/api/user', function (Request $request) {
return [
'has_pin' => UserSecurity::hasSecurityPin($request->user()),
'has_2fa' => UserSecurity::isTwoFactorEnabled($request->user()),
];
});
Migration Conflicts
users table already exists, manually add the columns or drop the table before migrating.security_pin VARCHAR(255) NULL,
mnemonic_key VARCHAR(255) NULL,
two_factor_secret VARCHAR(255) NULL,
two_factor_recovery_codes TEXT NULL
Pin Length Validation
config/user-security.php). Ensure your UI enforces this.'pin' => [
'length' => 6, // Now requires 6-digit pins
],
Mnemonic Key Collisions
if (UserSecurity::mnemonicExists($mnemonic)) {
// Regenerate
}
2FA Secret Storage
two_factor_secret is stored in plaintext. Encrypt it if compliance requires:
use Illuminate\Support\Facades\Crypt;
$encryptedSecret = Crypt::encrypt($secret);
UserSecurity::setTwoFactorSecret($user, $encryptedSecret);
$secret = Crypt::decrypt(UserSecurity::getTwoFactorSecret($user));
Recovery Code Exhaustion
if (UserSecurity::isRecoveryCodeUsed($user, $code)) {
return back()->withErrors(['code' => 'Used or invalid.']);
}
Laravel Caching
has_pin or is_2fa_enabled flags:
$user->cache([has_pin => UserSecurity::hasSecurityPin($user)]);
Log Security Events
Add logging to UserSecurityServiceProvider:
public function boot()
{
\Log::info('UserSecurity initialized');
// ...
}
Or wrap calls in logs:
\Log::debug('Validating pin for user', ['user_id' => $user->id, 'pin' => $pin]);
Test Pin/2FA Locally Use Tinker to test:
How can I help you explore Laravel packages today?