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

Next.js+Firebase邀请注册问题:sendEmailVerification()无法获取用户邮箱

解决Next.js + Firebase邀请式注册的邮箱获取问题

你用sendEmailVerification()来实现邀请注册是找错了工具——这个API的设计目的是给已创建的用户发送邮箱验证邮件,它的oobcode仅用于验证邮箱,无法反向获取用户邮箱,这就是你遇到问题的核心原因。

正确的做法是使用Firebase的密码重置邮件流程来实现邀请注册,因为密码重置的oobcode支持获取邮箱,且天然满足「1天过期」「一次性使用」的要求。以下是完整实现步骤:

一、管理员端:发送邀请邮件(创建禁用用户+发送密码重置链接)

管理员通过Admin SDK创建一个禁用状态的用户(避免用户未设置密码就登录),然后发送密码重置邮件,配置链接有效期为1天:

// pages/api/send-invite.js (Next.js API路由)
import admin from 'firebase-admin';

// 初始化Firebase Admin(仅初始化一次)
if (!admin.apps.length) {
  admin.initializeApp({
    credential: admin.credential.cert({
      // 你的Admin SDK密钥信息,从Firebase控制台下载
      projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
    }),
  });
}

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: '仅支持POST请求' });
  }

  const { email } = req.body;
  try {
    // 1. 创建禁用状态的用户
    await admin.auth().createUser({
      email,
      disabled: true, // 禁用用户,直到完成密码设置
    });

    // 2. 发送密码重置邮件(作为邀请链接)
    const actionCodeSettings = {
      url: `${process.env.NEXT_PUBLIC_APP_URL}/set-password`,
      expirationTime: Date.now() + 86400 * 1000, // 1天后过期(单位:毫秒)
    };
    await admin.auth().sendPasswordResetEmail(email, actionCodeSettings);

    res.status(200).json({ message: '邀请邮件已发送' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
}

二、用户端:处理邀请链接,设置密码

用户点击邮件链接后跳转到/set-password页面,提取URL中的oobCode,验证并获取邮箱,完成密码设置后启用用户:

// pages/set-password.js
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import { getAuth, verifyPasswordResetCode, confirmPasswordReset } from 'firebase/auth';
import { initializeApp } from 'firebase/app';

// 初始化Firebase客户端
const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
};
initializeApp(firebaseConfig);

export default function SetPasswordPage() {
  const [password, setPassword] = useState('');
  const [email, setEmail] = useState('');
  const [error, setError] = useState('');
  const router = useRouter();
  const auth = getAuth();

  // 页面加载时验证oobCode并获取邮箱
  useEffect(() => {
    const urlParams = new URLSearchParams(window.location.search);
    const oobCode = urlParams.get('oobCode');

    if (!oobCode) {
      setError('无效的邀请链接');
      return;
    }

    verifyPasswordResetCode(auth, oobCode)
      .then((userEmail) => setEmail(userEmail))
      .catch(() => setError('邀请链接已过期或无效'));
  }, [auth]);

  const handleSubmit = async (e) => {
    e.preventDefault();
    const urlParams = new URLSearchParams(window.location.search);
    const oobCode = urlParams.get('oobCode');

    try {
      // 1. 确认密码重置(自动设置用户密码)
      await confirmPasswordReset(auth, oobCode, password);

      // 2. 启用用户(需要Admin SDK,这里可以调用自己的API)
      await fetch('/api/enable-user', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email }),
      });

      alert('密码设置成功!请登录');
      router.push('/login');
    } catch (err) {
      setError(err.message);
    }
  };

  if (error) {
    return <div className="error">{error}</div>;
  }

  return (
    <form onSubmit={handleSubmit} className="set-password-form">
      <h2>设置您的账号密码</h2>
      <p className="email-info">受邀邮箱:{email}</p>
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="输入8位以上密码"
        required
      />
      <button type="submit">确认设置</button>
    </form>
  );
}

三、补充:启用用户的API路由

创建/api/enable-user.js来启用用户(需要Admin SDK权限):

import admin from 'firebase-admin';

if (!admin.apps.length) {
  admin.initializeApp({
    credential: admin.credential.cert({
      projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
    }),
  });
}

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: '仅支持POST请求' });
  }

  const { email } = req.body;
  try {
    await admin.auth().updateUser(email, { disabled: false });
    res.status(200).json({ message: '用户已启用' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
}

关键说明

  1. 密码重置链接的oobCode天然满足「一次性使用」:一旦调用confirmPasswordReset,该code立即失效;
  2. 通过expirationTime配置1天有效期,超出时间的链接无法使用;
  3. 自定义邮件模板:在Firebase控制台的「Authentication」→「模板」中,修改密码重置邮件的标题和内容,使其更像邀请邮件(比如把标题改成「您已获得平台邀请,请设置密码」)。

内容的提问来源于stack exchange,提问作者vishnu vardhan Gowd

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 01:45:03