Documentation Index
Fetch the complete documentation index at: https://docs.superun.ai/llms.txt
Use this file to discover all available pages before exploring further.
一、整体架构
用户扫码 → 微信授权 → 携带 code 回调到我们网站 → 后端用 code 换取用户信息 → 创建/匹配用户 → 建立登录会话
流程图
涉及的系统
| 系统 | 角色 |
|---|
| 微信开放平台 (open.weixin.qq.com) | 提供扫码授权能力,返回授权 code |
| 前端应用 | 展示二维码、处理回调、建立会话 |
| Edge Function (prod-wechat-auth) | 后端服务,用 code 换取微信用户信息并创建用户 |
| Supabase Auth | 用户管理和会话管理 |
二、微信开放平台配置(微信侧操作)
2.1 注册并登录微信开放平台
- 访问 微信开放平台
- 使用管理员微信扫码登录
- 如果没有账号,需要先注册开发者账号(需企业资质)
2.2 创建网站应用
- 登录后进入 管理中心
- 点击 网站应用 → 创建网站应用
- 填写应用信息:
- 应用名称:你的产品名称
- 应用官网:
https://your-domain.com(你的线上域名)
- 应用图标:上传 108×108 像素的 PNG 图标
- 提交审核,等待微信审核通过(通常 1-3 个工作日)
重要:应用必须通过审核后才能使用扫码登录功能。未通过审核会报 redirect_uri 参数错误。
2.3 获取 AppID 和 AppSecret
审核通过后:
- 进入 管理中心 → 网站应用 → 点击你的应用
- 在应用详情页可以看到:
- AppID:
wxYOUR_OPEN_APP_ID(以 wx 开头的 18 位字符串)
- AppSecret:点击重置/查看获取(32 位十六进制字符串,仅显示一次,务必保存)
2.4 配置授权回调域
这是最关键的一步,域名必须完全匹配:
- 在应用详情页找到 开发信息 区块
- 点击 授权回调域 的「修改」
- 填入你的网站域名(不带 http:// 或 https://,不带路径):
- 点击保存
注意事项:
- 只填域名,不要加协议头和路径
- 域名必须与前端实际访问的域名完全一致
- 如果更换了域名,必须同步修改此处
2.5 域名验证文件(如需要)
微信可能要求你在网站根目录放置验证文件以证明域名所有权:
- 下载微信提供的验证文件(如
MP_verify_xxx.txt)
- 将文件部署到网站根目录,确保可通过
https://your-domain.com/MP_verify_xxx.txt 访问
- 文件内容必须以 纯文本 形式返回(Content-Type: text/plain)
已知限制:SPA 单页应用的路由会拦截所有路径返回 HTML。如果使用 SPA 框架,需要在服务器(如 Nginx)层面单独配置这些文件的路由规则。
三、我方平台配置
3.1 环境变量配置
前端环境变量(.env 文件)
VITE_WECHAT_OPEN_APP_ID=wxYOUR_OPEN_APP_ID
- 用途:前端直接读取此值来初始化微信扫码二维码
- 为什么放前端:避免 Edge Function 边缘节点缓存延迟导致新部署的接口暂时不可用
后端密钥(Edge Function Secrets)
通过密钥管理平台配置,不要写在代码中:
| 密钥名 | 值 | 说明 |
|---|
SUPERUN_WECHAT_OPEN_APP_ID | wxYOUR_OPEN_APP_ID | 开放平台 AppID |
SUPERUN_WECHAT_OPEN_APP_SECRET | YOUR_OPEN_APP_SECRET | 开放平台 AppSecret |
3.2 路由配置
在 App.tsx 中需要注册回调路由:
<Route path="/auth/wechat/callback" element={<WechatCallbackPage />} />
此路由不需要登录保护(ProtectedRoute),因为它就是登录流程的一部分。
3.3 Edge Function 配置
在 supabase/config.toml 中注册函数并关闭 JWT 验证(登录接口本身不需要已登录):
[functions.prod-wechat-auth]
verify_jwt = false
四、前端代码实现
4.1 二维码展示组件 — WechatQREmbed.tsx
职责:在登录页面内嵌展示微信扫码二维码。
核心逻辑:
// 1. 从环境变量读取 AppID
const appid = import.meta.env.VITE_WECHAT_OPEN_APP_ID;
// 2. 加载微信 JS SDK
const script = document.createElement("script");
script.src = "https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js";
// 3. 生成 CSRF 防护 state 并存入 sessionStorage
const state = crypto.randomUUID();
sessionStorage.setItem("wechat_oauth_state", state);
// 4. 构建回调地址
const redirectUri = `${window.location.origin}/auth/wechat/callback`;
// 5. 初始化二维码
new window.WxLogin({
self_redirect: false, // false = 扫码后当前窗口跳转
id: "wechat-qr-container", // 二维码容器 DOM 元素 ID
appid, // 开放平台 AppID
scope: "snsapi_login", // 固定值,网站应用扫码登录
redirect_uri: encodeURIComponent(redirectUri),
state, // CSRF state
style: "black", // 二维码样式:black / white
});
WxLogin SDK 参数说明:
| 参数 | 必填 | 说明 |
|---|
id | 是 | 放置二维码的 HTML 容器元素 ID |
appid | 是 | 开放平台应用的 AppID |
scope | 是 | 固定填 snsapi_login |
redirect_uri | 是 | 授权成功后的回调地址,需要 encodeURIComponent |
state | 是 | 随机字符串,防止 CSRF 攻击 |
self_redirect | 否 | true=iframe内跳转,false=当前页跳转 |
style | 否 | 二维码配色,black 或 white |
href | 否 | 自定义二维码区域的 CSS 样式链接 |
实际渲染效果:SDK 会在指定容器内创建一个 iframe,展示微信扫码二维码。用户扫码确认后,页面跳转到 redirect_uri?code=xxx&state=xxx。
4.2 回调处理页面 — WechatCallbackPage.tsx
职责:接收微信回调的 code,交给后端换取用户信息并建立登录会话。
核心流程:
- 从 URL 获取
code 和 state 参数
- 校验
state(CSRF 防护)
- 调用 Edge Function(
action: "login")
- 用返回的
token_hash 建立 Supabase 会话
- 跳转到首页
关键代码:
// 1. 从 URL 获取参数
const code = searchParams.get("code");
const returnedState = searchParams.get("state");
// 2. CSRF 校验:对比回调 state 和 sessionStorage 中保存的 state
const savedState = sessionStorage.getItem("wechat_oauth_state");
if (!savedState || savedState !== returnedState) {
// 安全校验失败,拒绝登录
return;
}
sessionStorage.removeItem("wechat_oauth_state");
// 3. 调用后端 Edge Function
const { data } = await supabase.functions.invoke("prod-wechat-auth", {
body: { action: "login", code },
});
// 4. 用返回的 token_hash 建立 Supabase 会话
await supabase.auth.verifyOtp({
token_hash: data.token_hash,
type: "magiclink",
});
// 5. 登录成功,跳转首页
navigate("/", { replace: true });
StrictMode 防重复:React StrictMode 会执行两次 useEffect,但微信的 code 是一次性的。使用 useRef 标记确保只处理一次:
const hasProcessed = useRef(false);
useEffect(() => {
if (hasProcessed.current) return;
hasProcessed.current = true;
// ... 处理逻辑
}, []);
4.3 登录页面集成 — LoginPage.tsx
登录页面支持三种登录方式的 Tab 切换:
| 模式 | 组件 | 说明 |
|---|
| 手机号登录 | 内置表单 | 手机号 + OTP 验证码 |
| 邮箱登录 | 内置表单 | 邮箱 + 密码 |
| 微信扫码 | <WechatQREmbed /> | 内嵌二维码 |
浏览器环境自适应:
import { isWechatBrowser } from "@/lib/wechat";
const inWechat = useMemo(() => isWechatBrowser(), []);
// 普通浏览器:默认展示手机号登录,微信扫码作为第三个 Tab
const [mode, setMode] = useState("phone");
微信浏览器检测方法(src/lib/wechat.ts):
export function isWechatBrowser(): boolean {
if (typeof navigator === "undefined") return false;
return /MicroMessenger/i.test(navigator.userAgent);
}
五、后端代码实现
5.1 Edge Function — prod-wechat-auth/index.ts
职责:接收前端传来的微信授权 code,向微信 API 换取用户信息,创建 Supabase 用户并返回登录凭证。
支持的 Action 列表
| Action | 用途 | 参数 |
|---|
get-qr-config | 返回开放平台 AppID(备用) | 无 |
get-login-url | 返回完整 OAuth URL(备用) | redirect_uri |
login | 开放平台扫码登录(主流程) | code |
核心登录函数 handleOAuthLogin
handleOAuthLogin(code, appId, appSecret, emailPrefix, providerLabel)
执行步骤:
步骤 1: code → access_token + openid
─────────────────────────────────────
请求: GET https://api.weixin.qq.com/sns/oauth2/access_token
参数: appid, secret, code, grant_type=authorization_code
返回: { access_token, openid, unionid }
步骤 2: openid → 微信用户信息
──────────────────────────────
请求: GET https://api.weixin.qq.com/sns/userinfo
参数: access_token, openid, lang=zh_CN
返回: { nickname, headimgurl, sex, province, city, ... }
步骤 3: 创建或匹配 Supabase 用户
────────────────────────────────
邮箱格式: wx_{openid}@wechat.user
如果用户已存在则跳过创建。
步骤 4: 生成 Magic Link 令牌
────────────────────────────
使用 admin.generateLink({ type: "magiclink", email })
返回 token_hash 给前端,前端用 verifyOtp 建立会话。
步骤 5: 更新用户元数据
────────────────────────────
将微信昵称、头像等信息写入 user_metadata,
确保用户每次登录都能获取最新的微信资料。
用户数据存储结构
每个微信用户在 Supabase Auth 中的记录:
{
"email": "wx_{openid}@wechat.user",
"user_metadata": {
"oauth_provider": "wechat_open",
"oauth_open_id": "{openid}",
"wechat_openid": "{openid}",
"wechat_unionid": "{unionid}",
"wechat_nickname": "微信昵称",
"avatar_url": "https://thirdwx.qlogo.cn/...",
"provider": "wechat"
}
}
读取的环境变量
| 变量名 | 来源 | 用途 |
|---|
SUPERUN_WECHAT_OPEN_APP_ID | Edge Function Secret | 开放平台 AppID |
SUPERUN_WECHAT_OPEN_APP_SECRET | Edge Function Secret | 开放平台 AppSecret |
SUPABASE_URL | 内置 | Supabase 项目地址 |
SUPABASE_SERVICE_ROLE_KEY | 内置 | Supabase 管理员密钥 |
六、安全机制
6.1 CSRF 防护(State 参数)
发起扫码 → 生成 UUID 作为 state → 存入 sessionStorage
↓
微信回调 → 携带 state → 对比 sessionStorage 中的值
↓
匹配 → 继续登录
不匹配 → 拒绝(可能是伪造请求)
6.2 Code 一次性使用
- 微信返回的
code 有效期 5 分钟,且只能使用一次
- React StrictMode 会导致 useEffect 执行两次,通过
useRef 标记防止重复调用
6.3 Secret 管理
AppSecret 仅存储在 Edge Function 的 Secrets 中,运行时通过 Deno.env.get() 读取
- 前端仅暴露
AppID(公开信息),不暴露 AppSecret
- 所有 code 换 token 的操作在后端完成
七、调试与排错
常见错误
| 错误信息 | 原因 | 解决方案 |
|---|
redirect_uri 参数错误 | 回调域名与开放平台配置的不一致;或应用未通过审核 | 检查开放平台「授权回调域」是否与实际域名完全一致;确认应用已通过审核 |
Invalid action | Edge Function 边缘节点缓存,新部署的代码未生效 | 等待几分钟后重试;或将相关配置改为前端环境变量 |
invalid code (errcode: 40029) | 授权码已过期或已被使用 | 重新扫码获取新的 code |
微信 SDK 加载失败 | 网络问题导致 wxLogin.js 加载失败 | 检查网络连接,点击”重新加载” |
安全校验失败 | CSRF state 不匹配 | 可能是用户在多个标签页操作,重新从登录页开始 |
调试步骤
-
检查二维码是否正常加载
- 打开浏览器开发者工具 → Network
- 确认
wxLogin.js 加载成功(200)
- 确认 iframe src 中的
appid 值正确
-
检查回调是否成功
- 扫码后观察地址栏是否跳转到
/auth/wechat/callback?code=xxx&state=xxx
- 如果没有跳转,说明微信侧配置有问题
-
检查后端接口
- 在 Supabase Dashboard → Edge Functions → Logs 中查看
prod-wechat-auth 的执行日志
- 日志中会输出每一步的详细信息(
[prod-wechat-auth] 前缀)
-
检查用户是否创建成功
- Supabase Dashboard → Authentication → Users
- 搜索
@wechat.user 查看微信用户记录
测试接口
可以用 curl 测试 Edge Function 是否正常响应:
# 测试 login 接口(应返回 "授权码 (code) 不能为空" 或 "invalid code",说明接口正常)
curl -X POST \
https://<PROJECT_ID>.supabase.co/functions/v1/prod-wechat-auth \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <SUPABASE_ANON_KEY>" \
-d '{"action": "login", "code": "test123"}'
八、配置清单(需替换为实际值)
| 项目 | 占位符 | 说明 |
|---|
| 开放平台 AppID | wxYOUR_OPEN_APP_ID | 微信开放平台 → 网站应用 → 应用详情获取 |
| 开放平台 AppSecret | YOUR_OPEN_APP_SECRET | 微信开放平台 → 网站应用 → 应用详情获取(仅显示一次) |
| 授权回调域 | your-domain.com | 与前端实际部署域名一致 |
| 回调路径 | /auth/wechat/callback | 固定值,与前端路由匹配 |
| Edge Function 名称 | prod-wechat-auth | 后端处理函数名 |
| 前端环境变量 | VITE_WECHAT_OPEN_APP_ID | 存放开放平台 AppID |
| 后端密钥 | SUPERUN_WECHAT_OPEN_APP_ID | Edge Function Secret 名称 |
| 后端密钥 | SUPERUN_WECHAT_OPEN_APP_SECRET | Edge Function Secret 名称 |
| Supabase 项目地址 | https://<PROJECT_ID>.supabase.co | Supabase Dashboard 获取 |
九、文件清单
| 文件路径 | 职责 |
|---|
src/components/desktop/WechatQREmbed.tsx | 微信扫码二维码展示组件 |
src/pages/desktop/WechatCallbackPage.tsx | OAuth 回调处理页面 |
src/pages/desktop/LoginPage.tsx | 登录页面(集成三种登录方式) |
src/lib/wechat.ts | 微信浏览器检测工具函数 |
supabase/functions/prod-wechat-auth/index.ts | 后端 OAuth 处理 Edge Function |
supabase/config.toml | Edge Function 配置(关闭 JWT 验证) |
.env | 前端环境变量(AppID) |
十、相关微信文档