跳到主要内容

公众号集成接入指南 · 网页授权篇

在 CloudBase 控制台创建「微信公众号」集成后,系统会自动生成一个 HTTP 云函数(函数名形如 <集成标识>-<随机串>,本文统称 offiaccount-common)。本指南只介绍网页授权(OAuth 2.0)——也是公众号集成最常用、最高频的能力,用于在公众号场景下让网页拿到用户的 openid 与(可选的)昵称/头像等信息。

其他能力(access_token 托管、JS-SDK、模板/订阅消息、客服消息、菜单、二维码、用户管理、素材等)由同一个 offiaccount-common 云函数提供,本指南不展开。

整体调用链路:

  • 网页跳转 → 微信授权页 → 微信回跳带回 code
  • 网页携带 code → CloudBase 云 API 网关(accessToken 鉴权)→ offiaccount-common HTTP 云函数 → 微信 sns/oauth2/access_token API
  • offiaccount-common 返回 { openid, access_token, ... } 给网页或业务后端

架构总览

┌──────────────────┐
│ 公众号网页 │
│ (微信浏览器) │
└────────┬─────────┘
│ 1. 跳转 open.weixin.qq.com/connect/oauth2/authorize
│ ?appid=...&redirect_uri=...&scope=snsapi_base&state=...

┌──────────────────┐
│ 微信授权页 │ 用户授权 / snsapi_base 静默
└────────┬─────────┘
│ 2. 回跳到 redirect_uri 携带 ?code=xxx&state=xxx

┌──────────────────┐
│ 公众号网页 │
└────────┬─────────┘
│ 3. POST { code } 到 /oauth/token,带 Bearer accessToken

┌──────────────────────────┐
│ CloudBase 云 API 网关 │ accessToken 鉴权
└────────┬─────────────────┘
│ HTTP invoke

┌────────────────────────────┐
│ offiaccount-common │
│ POST /oauth/token │
│ → 调微信 sns/oauth2/... │
│ ← 返回 openid + tokens │
└────────────────────────────┘

职责分工:

角色职责
公众号网页引导用户跳转微信授权页;回跳后把 code 发给后端
云 API 网关校验 Bearer {accessToken},将请求转发到 HTTP 云函数
offiaccount-common 云函数持有 AppID + AppSecret,用 code 调微信换 openid 与 OAuth tokens;按需调用 sns/userinfo 拉用户资料
微信公众平台颁发 codeopenid,下发用户资料
集成中心统一托管 AppID/AppSecret,作为环境变量注入云函数

前置条件

要求
公众号服务号或已开通 snsapi_base 权限的订阅号;已通过微信认证
公众号 AppID + AppSecret持有可用值,AppSecret 可在公众平台 → 基本配置中重置
网页授权域名公众平台 → 设置与开发 → 公众号设置 → 功能设置 → 网页授权域名,已添加发起授权的页面域名并完成 MP_verify_xxx.txt 校验
CloudBase 环境已开通;至少有一种认证登录方式可用(匿名登录不能调云函数,参见 4.3 节)

第一步 · 准备公众号凭证

登录 微信公众平台 → 设置与开发 → 基本配置:

  • AppID:形如 wxc1546068399a59fc
  • AppSecret:点击「重置」生成,仅显示一次,妥善保存

AppSecret 不要泄露给前端,CloudBase 集成中心会把它托管并注入到云函数环境变量。


第二步 · 在 CloudBase 创建集成

2.1 进入集成中心

路径:云开发平台模板与集成 → 集成中心 → 微信公众号 → 创建集成

2.2 填写集成信息

控制台向导需填写:

  • 集成名称(自定义,例如 offiaccount-demo
  • 公众号 AppIDAppSecret

集成中心将统一托管上述凭证。

2.3 自动生成云函数资源

创建成功后,系统自动完成两项操作:

  • 按集成名称创建一个 HTTP 云函数(函数名形如 <集成标识>-<随机串>,本文统称 offiaccount-common)。
  • 将 AppID、AppSecret 作为环境变量注入到该云函数。

第三步 · 了解 offiaccount-common 云函数

第二步创建集成时,平台已经把 AppID、AppSecret 作为环境变量注入到云函数。运行时通过 process.env 读取,业务代码中不会出现明文凭证。

如需修改配置,进入「集成中心 → 对应集成 → 编辑」,保存后平台将自动重新部署云函数。

3.1 网页授权相关路由

⚠️ 与「微信支付」集成的 pay-common 不同,offiaccount-common 不使用 _action 字段分发,而是直接使用 REST 路径。调用云函数时请使用完整路径,例如 POST /oauth/token

云函数对外暴露的网页授权 5 个 REST 路由:

路由用途必填请求体
POST /oauth/config返回当前集成配置的公众号 appId,便于前端拼授权 URL(避免在前端硬编码)
POST /oauth/tokencode 换取 openid + OAuth access_token + refresh_token{ code }
POST /oauth/refresh刷新 OAuth access_token(30 天有效的 refresh_token 换新的 2 小时 token){ refresh_token }
POST /oauth/userinfo用 OAuth access_token 拉取授权用户昵称/头像(仅 snsapi_userinfo scope 有效){ access_token, openid }
POST /oauth/verify校验 OAuth access_token 是否仍有效{ access_token, openid }

所有路由统一返回 { code: 0, msg: 'success', data: <业务数据> },错误返回 { code: -1, msg, data: null }

这里的 OAuth access_token 与公众号的全局 access_token(用于调菜单/消息接口)是两个不同的东西,前者只能用于拉取本用户的资料、不能调其他接口。

3.2 链路与鉴权

调用 offiaccount-common 的链路与 pay-common 完全一致:

前端登录 CloudBase 获取 accessToken → 携带 Authorization: Bearer {accessToken} 请求云 API 网关 → 网关分发至 offiaccount-common。

accessToken 必须来自「认证登录」——匿名登录拿到的 token 无权调用云函数,网关会直接返回 401。具体登录方式(邮箱、手机号、自定义登录等)请参考 CloudBase Web SDK · 认证登录


第四步 · 接入网页授权

4.1 前置准备

配置网页授权域名

路径:微信公众平台 → 设置与开发 → 公众号设置 → 功能设置 → 网页授权域名。

下载 MP_verify_xxx.txt,放到 https://<你的域名>/MP_verify_xxx.txt 可访问的位置,再点击校验通过。

该域名必须是发起授权页的域名,不是回跳处理页的域名(虽然两者通常相同)。

理解两种 scope

scope用户体验能拿到什么
snsapi_base静默授权,无弹窗openid(多数业务用这个)
snsapi_userinfo弹出授权确认页openid + 昵称 + 头像 + 性别等

4.2 完整网页授权流程

四步流程,前两步在前端完成,第三步走 offiaccount-common,第四步按需调用。

第 1 步:网页 → 跳转微信授权页
第 2 步:微信 → 回跳前端 redirect_uri,附带 ?code=xxx
第 3 步:前端 → POST /oauth/token { code } → 拿到 openid + access_token
第 4 步:前端 → POST /oauth/userinfo(仅 snsapi_userinfo 需要)

4.3 完整调用示例

适合微信内置浏览器中打开的公众号 H5 页面。

关键约束:

  • 必须在微信内置浏览器中打开页面,外部浏览器(Chrome / Safari)打开微信授权页会跳到提示页
  • 页面 URL 域名必须已配在公众号「网页授权域名」白名单
  • state 参数必填,用于防 CSRF(前端生成随机串,回跳后校验)
  • accessToken 必须来自认证登录,详见 3.2
<!-- oauth.html 发起授权页 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>正在授权…</title></head>
<body>
<script src="https://cdn.jsdelivr.net/npm/@cloudbase/js-sdk/dist/cloudbase.full.js"></script>
<script>
const ENV_ID = 'your-env-id'
const FN_NAME = 'offiaccount-common' // 集成创建后生成的实际函数名

const app = cloudbase.init({ env: ENV_ID })
const auth = app.auth({ persistence: 'local' })

// ⚠️ accessToken 必须来自「认证登录」,匿名登录无权调云函数
// 完整登录方式见 https://docs.cloudbase.net/api-reference/webv2/authentication#%E8%AE%A4%E8%AF%81%E7%99%BB%E5%BD%95
async function getAccessToken() {
// 示例占位:业务方需实现真实登录后返回 accessToken
throw new Error('请按 CloudBase Web SDK 文档实现认证登录后返回 accessToken')
}

async function callFn(token, path, body) {
const res = await fetch(
`https://${ENV_ID}.api.tcloudbasegateway.com/v1/functions/${FN_NAME}${path}?webfn=true`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(body || {}),
}
)
return res.json()
}

function unwrap(body) {
// offiaccount-common 直接返回 { code, msg, data },没有 wechatpay-node-v3 那层嵌套
return body && body.code === 0 ? body.data : null
}

;(async () => {
const token = await getAccessToken()
const url = new URL(location.href)
const code = url.searchParams.get('code')
const state = url.searchParams.get('state')

// —— 第 1 步:没有 code,发起授权 ——
if (!code) {
// 拿 AppID(也可直接在前端硬编码)
const { appId } = unwrap(await callFn(token, '/oauth/config')) || {}
if (!appId) { alert('未取到 appId'); return }

const randomState = Math.random().toString(36).slice(2, 18)
sessionStorage.setItem('oauth_state', randomState)

// 当前页作为 redirect_uri,回跳后再次进入本脚本
const redirectUri = encodeURIComponent(location.href.split('?')[0])
location.replace(
`https://open.weixin.qq.com/connect/oauth2/authorize` +
`?appid=${appId}` +
`&redirect_uri=${redirectUri}` +
`&response_type=code` +
`&scope=snsapi_base` + // 改成 snsapi_userinfo 可拉昵称/头像
`&state=${randomState}` +
`#wechat_redirect`
)
return
}

// —— 第 2 步:回跳带回 code,校验 state ——
const expectedState = sessionStorage.getItem('oauth_state')
if (state !== expectedState) {
alert('state 校验失败,疑似 CSRF')
return
}
sessionStorage.removeItem('oauth_state')

// —— 第 3 步:code 换 openid ——
const tokenRes = unwrap(await callFn(token, '/oauth/token', { code }))
if (!tokenRes || !tokenRes.openid) {
alert('换取 openid 失败')
return
}
const { openid, access_token, refresh_token, scope } = tokenRes
console.log('已拿到 openid:', openid, 'scope:', scope)

// —— 第 4 步(可选):snsapi_userinfo 才能拉资料 ——
if (scope === 'snsapi_userinfo') {
const info = unwrap(await callFn(token, '/oauth/userinfo', {
access_token,
openid,
}))
console.log('用户资料:', info) // { nickname, headimgurl, sex, ... }
}

// 业务侧:把 openid 写入会话 / 跳转到业务页
sessionStorage.setItem('openid', openid)
location.replace('/home')
})()
</script>
</body>
</html>

4.4 调用 /oauth/refresh/oauth/verify

OAuth access_token 默认 2 小时过期。若要在长会话场景下复用 openid 关联的资料拉取能力,可:

// 续期:用 refresh_token 换新的 access_token(refresh_token 30 天有效)
const refreshed = unwrap(await callFn(token, '/oauth/refresh', {
refresh_token,
}))
// refreshed = { openid, access_token, refresh_token, expires_in, scope }

// 校验:判断 token 是否仍有效
const { valid } = unwrap(await callFn(token, '/oauth/verify', {
access_token,
openid,
}))

大多数业务不需要持久化保存 OAuth access_token——拿到 openid 即可入库关联用户,其他公众号能力(消息/客服等)走全局 access_token(由 offiaccount-common/token/* 路由托管)。


常见问题 FAQ

Q1:调用 /oauth/token 返回「微信接口错误 [40029]: invalid code」

code 有三种情况会拿到这个错误:

  • 已被用过一次(code 只能消费一次,前端误重复调用,或刷新页面带 code 又跳一遍)
  • 已过期(5 分钟内有效)
  • 与发起授权时的 AppID 不一致(例如集成里填的是 A,授权 URL 用的是 B)

排查:清缓存重试,确认前端不会重入。

Q2:调用 /oauth/token 返回「invalid appsecret」或 40125

集成中心填写的 AppSecret 错误,或已被在公众平台重置。前往集成中心 → 编辑 → 重新粘贴 AppSecret。

Q3:授权页提示「该链接无法访问,请检查链接是否正确」/「redirect_uri 域名与后台配置不一致」

redirect_uri 的域名不在公众平台「网页授权域名」白名单。检查:

  • 域名是否完整匹配(含子域;公众平台只允许填主域,子域会自动覆盖)
  • 校验文件 MP_verify_xxx.txt 是否真的可以访问
  • 配置后建议等 1–2 分钟生效

Q4:snsapi_base 拿不到昵称头像

这是设计如此。snsapi_base 只返回 openid,要拉资料必须用 snsapi_userinfo,会弹用户确认框。

Q5:网页在企业微信、QQ 浏览器中能跑吗

不能。open.weixin.qq.com/connect/oauth2/authorize 只在微信内置浏览器中生效;外部浏览器会跳到引导页。

Q6:能否直接在前端调 https://api.weixin.qq.com/sns/oauth2/access_token

不能也不该。该接口强制需要 AppSecret,前端调用会暴露 AppSecret,且微信对 IP 来源有校验。这正是 offiaccount-common 存在的价值——把 AppSecret 留在服务端。

Q7:能否复用 pay-common 的同一个 CloudBase 环境

可以。offiaccount-commonpay-common 是两个独立的集成,分别生成各自的 HTTP 云函数,互不影响。前端按需求分别调用即可。


延伸阅读

主题链接
微信网页授权(官方文档)https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html
sns/oauth2/access_token 接口说明https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#1
CloudBase Auth · 认证登录https://docs.cloudbase.net/api-reference/webv2/authentication#%E8%AE%A4%E8%AF%81%E7%99%BB%E5%BD%95