Series Laravel & Google 2FA Thực chiến #5: Vô hiệu hóa 2FA và Đặc quyền Admin (Trạm cuối)
1. Hiểm họa của nút "Tắt 2FA" ngây thơ
Nhiều lập trình viên làm tính năng Tắt 2FA rất đơn giản: Frontend gọi API /disable-2fa, Backend vào DB set cột google2fa_secret = null. Xong!
Tại sao cách này là thảm họa? Giả sử Quản lý ga đi ra ngoài uống nước mà quên khóa màn hình máy tính (lúc này Session/Token vẫn còn sống). Kẻ gian đi ngang qua, thấy màn hình đang mở liền bấm nút "Tắt 2FA" trên giao diện. Thế là lớp bảo vệ mạnh nhất bị gỡ bỏ dễ dàng.
Quy tắc vàng (Re-authentication): Bất cứ khi nào thực hiện hành động phá hủy bảo mật (Đổi mật khẩu, Tắt 2FA, Đổi email), bạn BẮT BUỘC phải yêu cầu User chứng minh lại thân phận. Có 2 cách:
- Yêu cầu nhập lại Mật khẩu (Password).
- Yêu cầu nhập một mã OTP 6 số hiện tại.
2. Code API Tắt 2FA (Tự phục vụ - Self Service)
Trong file TwoFactorSetupController.php (hoặc ProfileController), chúng ta sẽ viết API cho phép người dùng tự tắt 2FA của chính họ, với điều kiện họ phải nhập đúng mật khẩu hiện tại.
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
class TwoFactorSetupController extends Controller
{
// ... (Các hàm setup và confirm ở bài trước) ...
/**
* Người dùng tự tắt 2FA (Bắt buộc xác thực lại mật khẩu)
*/
public function disable(Request $request)
{
$request->validate([
'password' => 'required|string'
]);
$user = $request->user();
// 1. Nếu chưa bật 2FA thì báo lỗi luôn
if (empty($user->google2fa_secret)) {
return response()->json([
'success' => false,
'message' => 'Tài khoản chưa được kích hoạt 2FA.'
], 400);
}
// 2. Tái xác thực (Re-authentication) bằng Mật khẩu
if (!Hash::check($request->password, $user->password)) {
return response()->json([
'success' => false,
'message' => 'Mật khẩu xác nhận không chính xác. Không thể vô hiệu hóa 2FA.'
], 403);
}
// 3. Xóa sổ mọi dấu vết 2FA trong Database
$user->google2fa_secret = null;
$user->two_factor_recovery_codes = null;
$user->save();
return response()->json([
'success' => true,
'message' => 'Đã vô hiệu hóa tính năng bảo mật 2 lớp thành công.'
]);
}
}
3. "God Mode": Quyền lực Reset 2FA của Super Admin
Bây giờ tới kịch bản xấu nhất: Nhân viên mất điện thoại, mất luôn cả Recovery Codes. Họ không thể qua được "Phòng chờ" ở Bài 3, nghĩa là họ không thể login vào hệ thống để gọi API disable ở trên. Họ hoàn toàn bị khóa ngoài cửa (Locked out).
Lúc này, họ phải cầm thẻ nhân viên đến gặp trực tiếp bộ phận IT / Quản trị viên hệ thống. Admin sau khi xác minh đúng nhân sự (bằng mắt thật), sẽ truy cập vào trang Quản trị (Admin Panel) và bấm nút "Reset 2FA" cho user đó.
Hãy tạo một Controller dành riêng cho Admin:
docker-compose exec app php artisan make:controller Api/Admin/UserController
Logic của Admin cực kỳ đơn giản vì nó dùng quyền lực cưỡng chế, không cần hỏi lại password của nhân viên:
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* Khôi phục (Xóa) 2FA cho một nhân viên bị mất thiết bị
*/
public function reset2FA(Request $request, $userId)
{
// TÌM NHÂN VIÊN
$employee = User::findOrFail($userId);
if (empty($employee->google2fa_secret)) {
return response()->json([
'success' => false,
'message' => 'Nhân viên này hiện không bật 2FA.'
], 400);
}
// XÓA 2FA BẰNG QUYỀN ADMIN
$employee->google2fa_secret = null;
$employee->two_factor_recovery_codes = null;
$employee->save();
// Optional: Ghi Log Hệ Thống (Audit Log) cực kỳ quan trọng
// Log::info("Admin {$request->user()->id} đã reset 2FA cho User {$employee->id}");
return response()->json([
'success' => true,
'message' => "Đã xóa 2FA thành công cho nhân viên: {$employee->email}. Họ đã có thể đăng nhập bình thường."
]);
}
}
4. Đăng ký Routes
Phơi bày các API này ra file routes/api.php và sử dụng hệ thống Phân quyền (AuthZ) mà chúng ta đã làm ở các series trước để bảo vệ API của Admin.
use App\Http\Controllers\Api\TwoFactorSetupController;
use App\Http\Controllers\Api\Admin\UserController as AdminUserController;
// Route cho Nhân viên (Yêu cầu login)
Route::middleware(['auth:api'])->prefix('2fa')->group(function () {
// ... các route setup/confirm ...
// Tự vô hiệu hóa 2FA
Route::post('/disable', [TwoFactorSetupController::class, 'disable']);
});
// Route cho Admin (Yêu cầu login + Có quyền 'reset_user_security')
Route::middleware(['auth:api', 'permission:reset_user_security'])
->prefix('admin/users')
->group(function () {
// Admin cưỡng chế xóa 2FA
Route::post('/{userId}/reset-2fa', [AdminUserController::class, 'reset2FA']);
});
Lời kết cho Series Google 2FA
Vậy là chúng ta đã hoàn tất bức tranh bảo mật 2FA chuẩn mực cho hệ thống Backend. Hãy cùng nhìn lại những gì chúng ta đã xây dựng:
- Bài 1: Cấu hình thuật toán TOTP, chuẩn bị Database để lưu trữ khóa bí mật.
- Bài 2: Vẽ luồng cấp phát Secret Key, tạo mã QR và xác thực vòng đầu (Setup Flow).
- Bài 3: Thiết kế "Phòng chờ 2FA" bằng cơ chế Token Exchange phi trạng thái (Stateless).
- Bài 4: Xây dựng hệ thống phao cứu sinh (Recovery Codes) băm (hash) mã an toàn tuyệt đối.
- Bài 5: Xử lý luồng vô hiệu hóa có điều kiện và cung cấp đặc quyền giải cứu cho Admin.
Bất kỳ module bảo mật nào khi đưa vào hệ thống thực tế cũng không chỉ dừng lại ở việc gọi một thư viện là xong. Nó là sự tinh tế trong việc quản lý bộ nhớ đệm (Cache), mã hóa dữ liệu (Encrypt/Hash), và xây dựng luồng UI/UX hợp lý để người dùng không tự làm khó mình.
Hãy tích hợp module này vào các dự án của bạn để nâng cấp lớp giáp bảo vệ lên tầm Enterprise! Cảm ơn bạn đã theo dõi sát sao toàn bộ series này!
All rights reserved