You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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' => '邮箱验证码无效或已过期']);
}

三、完整流程梳理

  1. 用户登录后,Laravel后端检查users.2fa_enabled字段:
    • 若为false,直接跳转仪表盘;
    • 若为true,跳转至2FA验证页面。
  2. 2FA页面加载时:
    • 前端初始化Firebase ReCaptcha并发送短信验证码;
    • 后端生成邮箱验证码,发送至用户邮箱并存入数据库。
  3. 用户输入验证码后:
    • 前端调用Firebase API验证短信验证码;
    • 前端调用Laravel接口验证邮箱验证码。
  4. 双验证码验证通过后,后端更新用户状态,前端跳转仪表盘;任一验证码错误则显示对应提示。

内容的提问来源于stack exchange,提问作者Muhammad Umaair

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 01:30:56