如何在Laravel+Inertia.js+Vue3项目中实现2FA双因素认证?
解决Laravel+Inertia+Vue3项目中2FA实现的问题
一、Firebase验证码与ReCaptcha的正确配置
1. 修复「Firebase is not defined」错误
你使用的是Firebase模块化SDK,不存在全局Firebase对象。确保所有操作都使用导入的方法,排查代码中是否有调用Firebase.xxx的旧逻辑,替换为对应模块化导入的API即可。
2. 解决ReCaptcha验证失败问题
- 确保DOM元素存在:Vue3组件中,不要在挂载前获取DOM元素,必须在
onMounted钩子中初始化ReCaptchaVerifier:<template> <div id="recaptcha-container"></div> <!-- 其他表单元素 --> </template> <script setup> import { onMounted, ref } from 'vue'; import { initializeApp } from 'firebase/app'; import { getAuth, RecaptchaVerifier } from 'firebase/auth'; const firebaseConfig = { // 你的Firebase配置 }; const app = initializeApp(firebaseConfig); const auth = getAuth(app); let recaptchaVerifier = null; const errorMessage = ref(''); onMounted(() => { const recaptchaContainer = document.getElementById('recaptcha-container'); recaptchaVerifier = new RecaptchaVerifier(recaptchaContainer, { size: 'invisible', callback: () => console.log('reCAPTCHA验证通过'), 'expired-callback': () => errorMessage.value = 'reCAPTCHA已过期,请重新获取验证码' }, auth); }); </script> - 检查Firebase控制台配置:确认项目域名已添加到Firebase控制台的「授权域名」列表,ReCaptcha站点密钥与项目正确关联。
- 重置ReCaptcha:验证失败时调用
recaptchaVerifier.reset()重新生成验证实例。
3. 正确生成与处理短信验证码
- 保存
confirmationResult:调用signInWithPhoneNumber后,必须保存返回的confirmationResult,用于后续校验验证码:const confirmationResult = ref(null); const sendSmsCode = async (phoneNumber) => { try { errorMessage.value = ''; const result = await signInWithPhoneNumber(auth, phoneNumber, recaptchaVerifier); confirmationResult.value = result; console.log('短信验证码已发送'); } catch (error) { errorMessage.value = `发送失败:${error.message}`; recaptchaVerifier.reset(); } }; - 邮箱验证码补充实现:Firebase仅处理短信验证,邮箱验证码需通过Laravel邮件系统实现:
// Laravel后端:生成并发送邮箱验证码 public function sendEmailCode(Request $request) { $user = Auth::user(); $code = rand(100000, 999999); // 存入数据库并设置5分钟过期时间 $user->update([ '2fa_email_code' => $code, '2fa_code_expires_at' => now()->addMinutes(5) ]); // 发送邮件(需提前创建TwoFactorCodeMail邮件类) Mail::to($user->email)->send(new TwoFactorCodeMail($code)); return Inertia::render('Auth/TwoFactorChallenge'); }
二、Vue3组件中处理验证码校验
1. 完整组件示例
<template> <div class="2fa-challenge"> <h2>双因素认证</h2> <div v-if="errorMessage" class="error-text">{{ errorMessage }}</div> <div id="recaptcha-container"></div> <input v-model="phoneCode" type="text" placeholder="输入短信验证码" maxlength="6"> <input v-model="emailCode" type="text" placeholder="输入邮箱验证码" maxlength="6"> <button @click="verifyCodes" class="verify-btn">验证</button> <button @click="resendCodes" class="resend-btn">重新发送验证码</button> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import { usePage, router } from '@inertiajs/vue3'; import { initializeApp } from 'firebase/app'; import { getAuth, RecaptchaVerifier, signInWithPhoneNumber } from 'firebase/auth'; const firebaseConfig = { // 你的Firebase配置 }; const app = initializeApp(firebaseConfig); const auth = getAuth(app); let recaptchaVerifier = null; const confirmationResult = ref(null); const errorMessage = ref(''); const phoneCode = ref(''); const emailCode = ref(''); const user = usePage().props.value.user; onMounted(() => { // 初始化ReCaptcha const container = document.getElementById('recaptcha-container'); recaptchaVerifier = new RecaptchaVerifier(container, { size: 'invisible' }, auth); // 自动发送验证码 sendSmsCode(user.phone); router.post('/send-email-code'); }); const sendSmsCode = async (phone) => { try { const result = await signInWithPhoneNumber(auth, phone, recaptchaVerifier); confirmationResult.value = result; } catch (err) { errorMessage.value = `短信发送失败:${err.message}`; recaptchaVerifier.reset(); } }; const verifyCodes = async () => { try { // 验证短信验证码 await confirmationResult.value.confirm(phoneCode.value); // 验证邮箱验证码(调用后端接口) const response = await router.post('/verify-2fa-code', { email_code: emailCode.value }); if (response.props.success) { router.visit('/dashboard'); } else { errorMessage.value = '邮箱验证码错误'; } } catch (err) { errorMessage.value = err.code === 'auth/invalid-verification-code' ? '短信验证码错误' : `验证失败:${err.message}`; } }; const resendCodes = () => { sendSmsCode(user.phone); router.post('/send-email-code'); errorMessage.value = ''; }; </script>
2. Laravel后端校验逻辑
// routes/web.php Route::post('/verify-2fa-code', [TwoFactorController::class, 'verify'])->middleware('auth'); // app/Http/Controllers/TwoFactorController.php public function verify(Request $request) { $request->validate(['email_code' => 'required|numeric']); $user = Auth::user(); // 校验邮箱验证码有效性 if ($user->2fa_email_code == $request->email_code && $user->2fa_code_expires_at > now()) { // 标记验证通过并清空验证码 $user->update([ '2fa_email_code' => null, '2fa_code_expires_at' => null, '2fa_verified_at' => now() ]); return Inertia::render('Dashboard', ['success' => true]); } return Inertia::render('Auth/TwoFactorChallenge', ['error' => '邮箱验证码无效或已过期']); }
三、完整流程梳理
- 用户登录后,Laravel后端检查
users.2fa_enabled字段:- 若为
false,直接跳转仪表盘; - 若为
true,跳转至2FA验证页面。
- 若为
- 2FA页面加载时:
- 前端初始化Firebase ReCaptcha并发送短信验证码;
- 后端生成邮箱验证码,发送至用户邮箱并存入数据库。
- 用户输入验证码后:
- 前端调用Firebase API验证短信验证码;
- 前端调用Laravel接口验证邮箱验证码。
- 双验证码验证通过后,后端更新用户状态,前端跳转仪表盘;任一验证码错误则显示对应提示。
内容的提问来源于stack exchange,提问作者Muhammad Umaair
相关产品推荐
相关产品推荐

