Skip to content

微信网页授权接入协议 ​

本项目是服务号网页授权的前端回调中转站,负责接收并传递 code 和 state。服务号 AppSecret、换取 access_token、验证 state 和登录会话均在调用方后端完成。当前没有实现后端登录,也不会把“收到 code”显示为“登录成功”。

回调入口 ​

场景推荐回调路径必填业务参数接收方式
微信内网页/callback/webscene=1、originlocation.replace 返回完整原网页 URL
小程序 web-view/callback/miniscene=2、minipathwx.miniProgram.redirectTo 返回原生小程序页面

为兼容只传 scene 的调用方式,根路径 /?scene=1...、/?scene=2... 同样支持。独立路径与 scene 不一致会报错。微信追加的 code 和调用方原样返回的非空 state 也必须存在;重复参数、不支持的 scene、缺少 code 等情况展示无标题错误弹窗,不自动重新授权。

回调域名默认为 webauth.wx.zhang520.cn。VITE_AUTH_CALLBACK_ORIGIN 是构造链接的公开配置;回调接收按实际访问域名运行。更换域名时,同时更新调用方构造链接、部署站点、服务号网页授权域名和小程序业务域名。

场景一:返回原网页 ​

origin 虽然名为 origin,实际是完整 URL,例如 https://demo.example/home/index?foo=bar#section,必须使用 HTTPS。每一层查询参数仅编码一次:

ts
import { buildAuthorizeUrl } from "@/auth/protocol";
import { callbackOrigin } from "@/auth/config";

// 在原网站使用时,可复制 protocol.ts。state 由原网站后端生成、保存并绑定用户会话。
const authorizationUrl = buildAuthorizeUrl({
  appId: "wx你的服务号AppID",
  state: stateFromBackend,
  scope: "snsapi_base",
  callbackOrigin,
  destination: {
    scene: "1",
    origin: "https://demo.example/home/index?foo=bar#section",
  },
});
window.location.assign(authorizationUrl);

构造出的授权 URL 参数顺序遵循官方示例,包含 #wechat_redirect。snsapi_userinfo 应由用户点击按钮发起,避免页面加载时直接拉起该授权。

微信返回中转站后,结果示意为:

text
https://demo.example/home/index?foo=bar&code=CODE&state=STATE#section

原 URL 的其他查询参数和 hash 保留,旧的 code/state 全部替换。hash 路由项目也从 window.location.search 读取结果,而非从 hash 查询中读取。调用方拿到结果后提交给后端验证 state、一次性换取授权信息,再用 history.replaceState 清除地址栏中的 code/state。

禁止 HTTP、相对地址、javascript URL、含账号密码的地址和指向中转站自身的地址。可通过 VITE_ALLOWED_WEB_ORIGINS 设置精确允许列表(例如 https://demo.example,https://app.example:8443);空值按需求允许任意外部 HTTPS 原网页。生产环境建议填写实际接入网站,以限制 code 被转发的目的地;前端允许列表不能替代后端 state 与业务目标的绑定校验。

场景二:可靠返回小程序 ​

传输与时序 ​

  1. 专用授权页加载 web-view,完成微信网页授权,回调到 /callback/mini?scene=2&minipath=...&code=...&state=...。
  2. H5 保留参数在内存中并清除自身地址栏的 code/state;加载微信官方 JS-SDK 1.6.0,等待 WeixinJSBridgeReady,通过 getEnv 确认小程序环境。
  3. H5 调用 postMessage({ data: { type: 'WECHAT_WEB_AUTH_RESULT', version: 1, code, state } }),作为补充通知。
  4. 不等待 message 事件,立即调用 redirectTo({ url: '/pages/auth-result/index?code=...&state=...' })。
  5. 目标小程序页从 onLoad(options) 接收参数。旧专用授权页被替换销毁,无需占用页面栈或等待额外确认。

官方 postMessage 在后退、组件销毁、分享、复制链接等特定时机才触发 bindmessage,且 event.detail.data 是消息数组。它不是实时通信接口。不能假定它先于目标页 onLoad,更不能在 bindmessage 中再次跳转、重复交换 code。本方案让导航 URL 携带完整结果,因此即使消息延迟或缺失,目标页也能独立接收。收到消息、桥接 API 回调和页面隐藏均不等于业务后端登录成功。

SDK/桥接/环境检测/跳转等待均有超时提示;失败只允许用户手动重试,不循环导航。跳转 API 未报错但 10 秒后当前页仍未离开,也显示重试提示。真实网络、用户取消、微信限制或错误路径仍可能导致失败,不能以此保证所有设备无条件成功。

minipath 约定 ​

  • 必须是当前小程序内已注册的非 tabBar 页面,使用绝对路径,例如 /pages/auth-result/index 或 /package-auth/pages/result/index?from=home。
  • 不允许 URL、相对路径、hash、路径穿越,不应指向专用 web-view 授权页自身。
  • 原查询参数保留,旧 code/state 替换。原生小程序目标页将 options 中的结果 decodeURIComponent 一次;不要对已解码的 postMessage 数据再次解码。
  • 目标页应使用 onLoad 接收,不使用 onShow 再次交换 code。tabBar 业务需先经过一个普通接收页,验证并处理结果后再 switchTab;后者不支持通过 URL 查询参数传递结果。
  • 可通过 VITE_ALLOWED_MINI_PATHS 设置精确路由列表(不含查询参数)。

可复制的小程序示例 ​

仓库 examples/miniprogram/ 提供原生小程序示例,未修改其他项目:

text
utils/auth-session.js          state 关联、消息补充接收、单次消费
pages/web-auth/index.*         专用 web-view 授权页
pages/auth-result/index.*      从 onLoad 接收结果的普通页面
  1. 将 utils 和 pages 文件复制到小程序相应目录,按实际目录调整 require 路径。示例 package.json 仅用于仓库中的 Node 测试,不需要复制。
  2. 在小程序 app.json 的 pages 注册 pages/web-auth/index 和 pages/auth-result/index,后者不要加入 tabBar。
  3. 修改 web-auth/index.js 中的 SERVICE_APP_ID、CALLBACK_ORIGIN、MINI_PATH。必须使用已认证服务号 AppID,不是小程序 AppID。
  4. 原调用页取得后端随机 state 后,先建立本地关联,再打开授权页:
js
const { beginAuth } = require("../../utils/auth-session");
// stateFromBackend 来自你自己的后端授权事务接口,不能使用固定值或 Math.random。
beginAuth(stateFromBackend);
wx.navigateTo({
  url: "/pages/web-auth/index?state=" + encodeURIComponent(stateFromBackend),
});
  1. auth-result/index.js 已取得 result.code 和 result.state,在标注位置接入业务后端。后端必须校验 state 对应当前用户会话与目标业务,并对授权事务实现幂等处理;网络超时应查询该事务状态,不盲目重复兑换 code。

示例使用内存事务表,五分钟过期、只消费一次;小程序进程重启或 state 不匹配会要求重新授权。bindmessage 只保存补充通知,不写持久存储、不触发登录、不干扰结果页。示例没有虚构后端地址,接收参数后的业务登录逻辑需由实际项目对接。

部署 ​

  1. pnpm install --frozen-lockfile,配置 .env.local 或部署环境变量后执行 pnpm test、pnpm build。VITE_ 配置在构建时注入,修改后需重新构建。
  2. 将 dist 部署至回调域名的 HTTPS 站点根目录。配置 SPA 路由回退,保证直接访问 /callback/web、/callback/mini 能返回 index.html。
  3. 服务号后台「网页授权域名」填写 webauth.wx.zhang520.cn,不带协议或路径;按微信要求放置校验文件。
  4. 小程序配置该站点的「业务域名」并部署校验文件,检查账号资质、授权链路涉及的域名及真机访问能力。仅关闭开发者工具域名校验不能代替正式配置。
  5. 回调响应使用 Cache-Control: no-store、Referrer-Policy: no-referrer,避免在访问日志中记录查询字符串,不注入统计或第三方追踪脚本。

Nginx 路由与缓存完整配置见阿里云 ESA 加速与缓存。回调和带参数的根路径继续 no-store;无参数首页及构建哈希资源可缓存。需要根据原始 request_uri 区分,避免内部重写到 index.html 后丢失回调的禁缓存策略。

正式部署还应检查反向代理/CDN 的日志与缓存配置。微信 code 单次使用且五分钟过期,AppSecret 和 access_token 仅保留在后端。本项目不校验来自另一个 origin 的用户会话,调用方后端验证 state 是必需步骤。

真机验收 ​

  • 微信内浏览器:完整原 URL、既有查询参数、中文参数、hash、旧 code/state 替换;验证后端 state 与 code 兑换。
  • iOS / Android 微信小程序:授权完成后目标页收到准确参数,专用 web-view 页从页面栈销毁;分别检查消息早到、晚到、未到时只处理一次。
  • 用户拒绝授权、缺参、未知 scene、无效目标、SDK 加载失败、桥接超时、目标未注册、tabBar 目标、刷新、返回和重复打开。
  • 将结果提交后端后模拟网络超时,验证事务幂等和过期后的重新授权。

自动化测试覆盖编码与协议校验、桥接就绪/超时/失败、小程序消息时序和单次消费;不能替代微信真机、后台域名配置和业务后端联调。

官方依据:微信网页授权、web-view、redirectTo。

微信服务号网页授权 · 接入与部署文档
皖ICP备2021000025号