To implement email-based multi-factor authentication (MFA) in a Laravel application, you can follow these steps. This approach involves sending a unique token to the user's email, which they can click to verify their identity.
Step 1: Set Up Email Configuration
Ensure your Laravel application is configured to send emails. Update your .env file with the necessary email configuration:
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
[email protected]
MAIL_PASSWORD=your_email_password
MAIL_ENCRYPTION=tls
[email protected]
MAIL_FROM_NAME="${APP_NAME}"
Step 2: Create a Migration for MFA Tokens
Create a migration to store MFA tokens:
php artisan make:migration create_mfa_tokens_table
In the migration file, define the schema:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateMfaTokensTable extends Migration
{
public function up()
{
Schema::create('mfa_tokens', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('token');
$table->timestamp('expires_at');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('mfa_tokens');
}
}
Run the migration:
php artisan migrate
Step 3: Generate and Send MFA Token
Create a method to generate and send the MFA token to the user's email:
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use App\Models\MfaToken;
use App\Models\User;
public function sendMfaToken(User $user)
{
$token = Str::random(40);
$expiresAt = now()->addMinutes(10);
MfaToken::create([
'user_id' => $user->id,
'token' => $token,
'expires_at' => $expiresAt,
]);
$verificationUrl = route('mfa.verify', ['token' => $token]);
Mail::send('emails.mfa', ['url' => $verificationUrl], function ($message) use ($user) {
$message->to($user->email);
$message->subject('Your MFA Verification Link');
});
}
Step 4: Create the Verification Route and Controller
Define a route for verifying the MFA token:
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\MfaController;
Route::get('/mfa/verify/{token}', [MfaController::class, 'verify'])->name('mfa.verify');
Create the MfaController:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\MfaToken;
use Carbon\Carbon;
class MfaController extends Controller
{
public function verify($token)
{
$mfaToken = MfaToken::where('token', $token)->first();
if (!$mfaToken || $mfaToken->expires_at < now()) {
return redirect('/login')->withErrors(['message' => 'Invalid or expired MFA token.']);
}
// Mark the device as trusted or log the user in
// For example:
auth()->loginUsingId($mfaToken->user_id);
// Delete the token after successful verification
$mfaToken->delete();
return redirect('/home')->with('status', 'Device trusted successfully.');
}
}
Step 5: Create the Email View
Create a Blade view for the MFA email (resources/views/emails/mfa.blade.php):
<!DOCTYPE html>
<html>
<head>
<title>MFA Verification</title>
</head>
<body>
<p>Click the link below to verify your device:</p>
<p><a href="{{ $url }}">{{ $url }}</a></p>
</body>
</html>
Step 6: Integrate MFA in Your Authentication Flow
Modify your authentication flow to include the MFA step. For example, after a successful login, send the MFA token:
public function login(Request $request)
{
// Validate the request...
if (auth()->attempt($request->only('email', 'password'))) {
$this->sendMfaToken(auth()->user());
auth()->logout();
return redirect('/login')->with('status', 'We have sent you an MFA verification link. Please check your email.');
}
return back()->withErrors(['email' => 'Invalid credentials.']);
}
This solution provides a simple and user-friendly way to implement email-based multi-factor authentication in a Laravel application.