Installation:
composer require mustafa-awami/lara2fa
php artisan vendor:publish --provider="MustafaAwami\Lara2fa\Lara2faServiceProvider" --tag="migrations"
php artisan migrate
users table (adds two_factor_secret, two_factor_enabled, recovery_codes, passkey_credentials columns).Configure:
php artisan vendor:publish --provider="MustafaAwami\Lara2fa\Lara2faServiceProvider" --tag="config"
config/lara2fa.php:
otp (Email OTP): length, expire_minutes, attempts.totp: issuer, algorithm (SHA1/SHA256/SHA512).passkey: rp_name, rp_id, timeout_seconds.recovery_codes: count, expiry_days.First Use Case: Enable 2FA for a user via TOTP (e.g., in a controller):
use MustafaAwami\Lara2fa\Facades\Lara2fa;
// Generate and display QR code for authenticator app
$secret = Lara2fa::generateTotpSecret();
$qrCodeUrl = Lara2fa::getTotpQrCodeUrl($user->email, $secret);
return view('2fa.setup', compact('qrCodeUrl', 'secret'));
// Verify user input
$valid = Lara2fa::verifyTotpCode($secret, $userInputCode);
if ($valid) {
Lara2fa::enableTotp($user);
}
// Generate secret and QR code
$secret = Lara2fa::generateTotpSecret();
$qrCodeUrl = Lara2fa::getTotpQrCodeUrl($user->email, $secret);
// Store secret temporarily (e.g., in session)
session(['two_factor_secret' => $secret]);
// Verify user input
$valid = Lara2fa::verifyTotpCode($secret, $userInputCode);
if ($valid) {
Lara2fa::enableTotp($user); // Saves to DB
}
if (Lara2fa::isTotpEnabled($user)) {
$code = request('two_factor_code');
if (!Lara2fa::verifyTotpCode($user->two_factor_secret, $code)) {
return back()->withErrors(['two_factor_code' => 'Invalid code.']);
}
}
$otp = Lara2fa::generateEmailOtp($user->email);
Lara2fa::sendEmailOtp($user->email, $otp); // Uses Laravel Mail
$valid = Lara2fa::verifyEmailOtp($user->email, $userInputCode);
$challenge = Lara2fa::createPasskeyChallenge();
return view('passkey.register', compact('challenge'));
// Handle credential creation
$credential = Lara2fa::finishPasskeyRegistration(
$challenge,
request()->all(),
$user->email
);
$challenge = Lara2fa::createPasskeyChallenge();
return view('passkey.login', compact('challenge'));
$verified = Lara2fa::verifyPasskey(
$challenge,
request()->all(),
$user->passkey_credentials
);
$codes = Lara2fa::generateRecoveryCodes($user);
return view('2fa.recovery', compact('codes'));
$valid = Lara2fa::verifyRecoveryCode($user, $userInputCode);
if ($valid) {
Lara2fa::consumeRecoveryCode($user, $userInputCode);
}
Middleware for 2FA Checks: Create a middleware to enforce 2FA during login:
// app/Http/Middleware/CheckTwoFactor.php
public function handle(Request $request, Closure $next) {
if (auth()->check() && Lara2fa::isTwoFactorEnabled(auth()->user())) {
if (!$request->session()->has('two_factor_verified')) {
return redirect()->route('two.factor.verify');
}
}
return $next($request);
}
Fallback Logic: Combine methods for resilience:
if (Lara2fa::isTotpEnabled($user)) {
if (!Lara2fa::verifyTotpCode($user->two_factor_secret, $code)) {
// Fallback to recovery codes
if (!Lara2fa::verifyRecoveryCode($user, $code)) {
return back()->withErrors(['code' => 'Invalid TOTP or recovery code.']);
}
}
}
Rate Limiting: Use Laravel’s throttling for OTP attempts:
Route::middleware(['throttle:5,1'])->post('/verify-otp', ...);
Customizing Emails:
Extend the MustafaAwami\Lara2fa\Mail\OtpEmail class to modify OTP email templates.
Passkey UI:
Use the webauthn JavaScript library to handle credential creation/verification in the frontend:
// Example for registration
const credential = await navigator.credentials.create({
publicKey: JSON.parse(@json($challenge)),
});
Session Management:
Passkey Browser Support:
if (!Lara2fa::isWebAuthnSupported()) {
return view('fallback-2fa');
}
Recovery Code Expiry:
'recovery_codes' => [
'expiry_days' => 90,
],
TOTP Secret Storage:
two_factor_secret is stored in plaintext in the DB.enableTotp method).Email OTP Collisions:
session()->put('otp_token', Str::random(40))).TOTP Verification Failures:
timeStep (default: 30 seconds) matches the authenticator app’s settings.Lara2fa::getTotpCurrentCode($secret) to debug expected codes.Passkey Errors:
challenge and origin in the WebAuthn response match the server-side values.JSON.stringify(publicKeyCredential) for debugging.OTP Not Received:
config/mail.php).Custom OTP Generators: Override the OTP generator for non-numeric codes:
Lara2fa::extend('otp', function ($email) {
return Str::random(8); // Alphanumeric OTP
});
Multi-Factor Policies: Combine methods dynamically:
Lara2fa::extend('policy', function ($user) {
return [
'required' => ['totp', 'recovery_codes'],
'fallback' => ['email_otp'],
];
});
Event Listeners:
Listen for 2FA events (e.g., TwoFactorEnabled):
// app/Providers/EventServiceProvider.php
protected $listen = [
\MustafaAw
How can I help you explore Laravel packages today?