修复微信小程序中的 Cloudbase 认证常见报错
一句话定义:按错误码对号入座,快速定位微信小程序接入 Cloudbase 自定义登录时最常见的 10 个报错并修复。
预计耗时:10-20 分钟(取决于撞到哪个错) | 难度:入门
适用场景
适用于已经按 add-auth-wechat-miniprogram 跑通基础流程、但在某一步撞到具体报错的场景。按错误码对号入座,每个小节按「现象 → 根因 → 修复(带代码)→ 预防」四段组织,目的是让你最短时间内看懂为什么报错,而不是只知道怎么绕过。
- 适用:已经接入 Cloudbase 自定义登录并撞到了报错的小程序项目
- 不适用:还没跑通基础接入流程的项目,先看 add recipe 把环境搭起来再回来
用法:开发者工具 Console 面板 → 复制报错关键词 → 在下面的目录里 Ctrl+F → 直接跳到对应小节。
环境要求
沿用 add recipe 里的环境:
@cloudbase/js-sdk@2.27.3+@cloudbase/adapter-wx_mp@1.3.1(小程序端)@cloudbase/node-sdk@3.18.1(云函数)- 微信开发者工具 ≥
1.06.x
错误 1 — require is not defined(小程序端)
现象:小程序编译后 Console 直接报 ReferenceError: require is not defined,通常指向 libs/cloudbase.js 或 miniprogram_npm/@cloudbase/js-sdk/index.js。
根因:没有执行「构建 npm」。微信小程序的 runtime 不直接认 node_modules,必须用开发者工具把 node_modules 的依赖构建到 miniprogram_npm/ 目录才能用。
修复:
- 微信开发者工具 → 工具 → 构建 npm
- 检查项目根目录出现了
miniprogram_npm/@cloudbase/和miniprogram_npm/@cloudbase/adapter-wx_mp/ - 重新编译
预防:把"构建 npm"加到团队的本地启动 checklist 里。每次新装依赖或换分支切到 package.json 有改动的版本后都要重新构建,不会自动触发。
错误 2 — 微信返回 errcode: 40029
现象:云函数日志里出现:
{ "errcode": 40029, "errmsg": "invalid code, hints: ..." }
根因:wx.login 返回的 code 已经被用过一次,或者已经过期(有效期 5 分钟)。微信官方规则是 code 换 session 只能换一次,再次调用就报 40029。
修复:每次登录流程都重新调 wx.login,不要把 code 缓存到 storage 里或者跨页面复用:
// ❌ 不要这样
const cachedCode = wx.getStorageSync('wx_login_code');
// ✅ 每次都新拿
const { code } = await new Promise((resolve, reject) => {
wx.login({ success: resolve, fail: reject });
});
预防:ensureLogin 函数内部不要有任何绕开 wx.login 的快捷路径。所有检查「是否已登录」的逻辑应该通过 auth.hasLoginState(),而不是自己做一个 code 缓存。
错误 3 — 微信返回 errcode: 45011
现象:
{ "errcode": 45011, "errmsg": "api minute-quota reach limit" }
根因:jscode2session 接口对同一个 IP 有每分钟调用上限。这个错在灰度放量的时 候最容易撞,单人调试时几乎看不到。
修复:在云函数里给 ticket 加一层缓存,key 用 openid,TTL 设 5-10 分钟(ticket 本身的有效期就是 10 分钟,缓存更久没意义)。最简单的方案是用云数据库当 cache:
const db = app.database();
const TICKET_CACHE = 'login_ticket_cache';
async function getCachedTicket(openid) {
const res = await db
.collection(TICKET_CACHE)
.where({ openid })
.get();
if (res.data.length === 0) return null;
const record = res.data[0];
if (Date.now() - record.createdAt > 5 * 60 * 1000) return null;
return record.ticket;
}
async function saveTicketCache(openid, ticket) {
await db.collection(TICKET_CACHE).add({
data: { openid, ticket, createdAt: Date.now() },
});
}
如果流量更大,换 Redis(Cloudbase 扩展里有 Redis 实例)。
预防:QPS 压测时主动观察 45011 出现的阈值,超过阈值前提前加缓存,不 要等上线才撞。
错误 4 — INVALID_TICKET / TICKET_EXPIRED
现象:小程序端调 signInWithCustomTicket 报:
{ code: 'INVALID_TICKET', message: 'ticket is invalid or expired' }
根因:有三种可能,按概率排序:
tcb_custom_login.json不是控制台最新下载的那份——控制台每次点「重新生成私钥」,旧私钥会在2 小时后失效。这是最高频的原因,通常发生在多人协作场景,一个人重下了私钥,另一个人本地还是旧的- 云函数部署用了过期的私钥,ticket 签出来就是无效的
- ticket 在云函数和小程序之间传输时被截断(比如 HTTP 响应里被 JSON.stringify 两次)
修复:
# 1. 去控制台重新下载最新 tcb_custom_login.json
# 2. 覆盖云函数目录里的旧文件
cp ~/Downloads/tcb_custom_login.json cloudfunctions/getLoginTicket/
# 3. 重新部署
tcb fn deploy getLoginTicket --dir ./cloudfunctions/getLoginTicket -e your-env-id
预防:在云函数里打印 ticket 长度做 sanity check:
const ticket = app.auth().createTicket(openid);
console.log('[getLoginTicket] ticket length:', ticket.length);
正常 ticket 长度应该稳定在几百字符数量级,如果长度对不上就说明签出来就不对。
错误 5 — SYSTEM_NOT_OPEN(自定义登录未启用)
现象:
{ "code": "SYSTEM_NOT_OPEN", "message": "custom login is not enabled" }
根因:在 Cloudbase 控制台的「身份认证 → 登录方式」里,「自定义登录」没启用。控制台上可以下载私钥但不启用,这样 createTicket 不会报错,但签出来的 ticket 登录时会被拒绝。
修复:控制台 → 身份认证 → 登录方式 → 自定义登录 → 启用。
预防:把启用状态做成部署前置检查的一部分,或者写一个启动自检云函数在 CI 里跑。
错误 6 — customUserId 格式错误
现象:
createTicket failed: customUserId must match /^[A-Za-z0-9_\-#@(){}\[\]:.,<>+#~]{4,32}$/
根因:传给 createTicket 的第一个参数不是合法的 customUserId。规则是 4-32 位,字符只能是字母/数字和部分符号。
修复:检查 jscode2session 的返回值:
const session = await jscode2session(code);
if (session.errcode) {
// 微信侧错误,这里先 return
return { error: 'WX_API_ERROR', errcode: session.errcode };
}
if (!session.openid) {
// 理论上不会走到这里,但防御一下
return { error: 'NO_OPENID', raw: session };
}
const ticket = app.auth().createTicket(session.openid);
真实 openid 是 28 位字母数字,符合规则。出现这个错多半是因为微信接口报错时返回里没有 openid 字段,代码没做防御导致把 undefined 传给了 createTicket。
预防:任何对外部 API 的返回值都先验证关键字段再传下去,不要假设调用成功。
错误 7 — 小程序端报 NETWORK_ERROR/request:fail url not in domain list
现象:调云函数 HTTP 接口时:
request:fail url not in domain list
或者类似的域名白名单错误。
根因:微信小程序要求所有 wx.request 的目标域名必须在小程序后台的「服务器域名」白名单里。Cloudbase 云函数 HTTP 访问服务的 URL 默认不在。
修复:两步:
- 去微信公众平台 → 你的小程序 → 开发 → 开发管理 → 开发设置 → 服务器域名
- 在
request 合法域名里添加你的 Cloudbase HTTP 访问服务域名,形如https://your-env.service.tcloudbase.com
注意这里填的是域名不是完整 URL,不要带 path。
预防:开发时可以在开发者工具的「详情 → 本地设置」里勾选「不校验合法域名」快速绕过,但上线前必须在公众平台正式配置,否则体验版和正式版都会报这个错。
错误 8 — 签名错误 / SIGN_PARAM_INVALID
现象:云函数调 app.auth().createTicket() 时报:
SIGN_PARAM_INVALID: credential signature verification failed
根因:cloudbase.init 时传入的 credentials 路径读取到的 JSON 格式不对,或者是从错误的环境导出的私钥(比如你在 A 环境下载了私钥,但 env 字段填成了 B 环境的 ID)。
修复:
// 调试用:确认读到的 credentials 和当前 env 一致
const cred = require('./tcb_custom_login.json');
console.log('[getLoginTicket] cred env:', cred.env);
console.log('[getLoginTicket] init env:', process.env.TCB_ENV);
// 这两个必须相等
如果不相等,重新从目标环境的控制台下载私钥。
预防:tcb_custom_login.json 文件名不要直接作为唯一标识,建议在 CI/CD 里用环境前缀重命名,比如 tcb_custom_login.prod.json、tcb_custom_login.staging.json,从源头避免混环境。
错误 9 — auth is not a function(小程序端)
现象:
TypeError: cloudbase.useAdapters is not a function
或者
TypeError: app.auth is not a function
根因:@cloudbase/js-sdk 的版本不对。常见有两种情况:
- 装了
@cloudbase/js-sdk@1.x,这是老版本,API 和 2.x 不一样 - 装了
@cloudbase/js-sdk@next(即 v3),内置适配器后已经没有useAdapters方法
修复:锁定到 2.27.3:
cd miniprogram
npm install --save @cloudbase/js-sdk@2.27.3 @cloudbase/adapter-wx_mp@1.3.1
然后在 package.json 里把版本号锁死(不要用 ^):
{
"dependencies": {
"@cloudbase/js-sdk": "2.27.3",
"@cloudbase/adapter-wx_mp": "1.3.1"
}
}
重新「构建 npm」。
预防:小程序前端依赖版本一定要锁死到 patch,这是前面所有错误里不锁版本最容易撞的连锁反应。
错误 10 — 登录成功,但后续数据库/云函数调用报 UNAUTHORIZED
现象:signInWithCustomTicket 看起来成功了,auth.currentUser.uid 也能拿到,但接下来调 db.collection('xxx').get() 报 UNAUTHORIZED。
根因:登录成功只代表「知道你是谁」,但访问具体资源还要看「权限控制」那一层。Cloudbase 的新用户默认角色是「外部用户」,如果你的数据库集合权限设的是「仅管理员」或者「仅组织成员」,外部用户是读不了的。
修复:两种方式二选一。
方式 A——放宽集合权限(只适合公开数据):
控制台 → 数据库 → 集合 → 权限设置 → 所有用 户可读
方式 B——给这类用户分配角色(推荐生产环境用):
控制台 → 身份认证 → 权限控制 → 角色 → 给「外部用户」或自定义角色配置具体资源的策略。
预防:开发阶段先在控制台把数据库权限设成宽松模式(所有用户可读写),跑通后再一点点收紧。反过来从严到宽排查会浪费很多时间。
如果你撞到的错没列在上面
按这个顺序自查:
- 云函数日志——Cloudbase 控制台 → 云函数 → getLoginTicket → 日志。先确认 event 是否带着 code、
jscode2session返回了什么、createTicket是否被调用、返回了什么 - 小程序端 Console——确认
wx.request的 statusCode 和 data,很多问题其实是 HTTP 层而不是认证层 - Cloudbase 控制台 → 身份认证 → 用户管理——看新用户有没有真的落库,能看到就说明 ticket 环节通了
日志链条上缺失的那一环就是问题所在。