跳到主要内容

在微信小程序中接入 Cloudbase 用户认证

一句话定义:用 @cloudbase/js-sdk@2.27.3 + @cloudbase/adapter-wx_mp@1.3.1,让微信小程序通过云函数签发的 ticket 登录到一个独立的 Cloudbase 环境,登录成功后前端可以直接访问该环境的数据库、云存储和云函数。

预计耗时:25 分钟 | 难度:进阶

适用场景

这篇覆盖的场景是:你已经有一个独立的 Cloudbase 环境(在 tcb.cloud.tencent.com/dev 能看到那个 env-id),想让一个微信小程序作为前端接入它。

  • 适用:独立 Cloudbase 环境 + 微信小程序前端
  • 不适用:用的是微信开发者工具里的「云开发」(wx.cloud)。那个属于微信·云开发,是另一套体系,直接看 微信官方文档 就行,不需要这篇
  • 不适用:Web、UniApp、Taro 等,各有对应的 recipe

两套体系同名但不是一回事,一开始很容易搞混,所以特地写在最前面。

环境要求

依赖版本
Node.js(本地开发 + 云函数运行时)≥ 16.13
@cloudbase/js-sdk2.27.3
@cloudbase/adapter-wx_mp1.3.1
@cloudbase/node-sdk3.18.1
@cloudbase/clilatest(用来部署云函数)
微信开发者工具1.06.x(需要支持「构建 npm」)

另外需要:

  • 一个已开通的 Cloudbase 环境 ID
  • 微信开放平台或公众平台的小程序 AppID + AppSecret
  • 在 Cloudbase 控制台「身份认证 → 登录方式」里启用「自定义登录」,并下载私钥文件 tcb_custom_login.json

第一步:安装依赖

小程序前端——进入包含 app.js 的目录(通常叫 miniprogram/):

cd miniprogram
npm init -y
npm install --save @cloudbase/js-sdk@2.27.3 @cloudbase/adapter-wx_mp@1.3.1

装完之后在微信开发者工具里点击 工具 → 构建 npm。这一步必做,不然小程序运行时找不到包,会直接报 require is not defined

云函数目录另起:

mkdir -p cloudfunctions/getLoginTicket
cd cloudfunctions/getLoginTicket
npm init -y
npm install --save @cloudbase/node-sdk@3.18.1

把从控制台下载的 tcb_custom_login.json 放进 cloudfunctions/getLoginTicket/ 目录。这个文件带有管理员级别的签名私钥,不要提交到公开仓库,.gitignore 里加一行。

第二步:写云函数,用 wx.login 的 code 签发 ticket

cloudfunctions/getLoginTicket/index.js

const cloudbase = require('@cloudbase/node-sdk');
const https = require('https');

const WX_APPID = process.env.WX_APPID;
const WX_SECRET = process.env.WX_SECRET;
const TCB_ENV = process.env.TCB_ENV;

const app = cloudbase.init({
env: TCB_ENV,
credentials: require('./tcb_custom_login.json'),
});

exports.main = async (event, context) => {
const { code } = event;
if (!code) {
return { error: 'MISSING_CODE', message: 'event.code is required' };
}

let session;
try {
session = await jscode2session(code);
} catch (e) {
return { error: 'WX_API_UNREACHABLE', message: e.message };
}

if (session.errcode) {
return {
error: 'WX_API_ERROR',
errcode: session.errcode,
message: session.errmsg,
};
}

const { openid, unionid } = session;
const ticket = app.auth().createTicket(openid, {
refresh: 3600 * 1000, // access_token 刷新间隔 1 小时(毫秒)
});

return { ticket, openid, unionid };
};

function jscode2session(code) {
return new Promise((resolve, reject) => {
const url =
`https://api.weixin.qq.com/sns/jscode2session` +
`?appid=${WX_APPID}` +
`&secret=${WX_SECRET}` +
`&js_code=${code}` +
`&grant_type=authorization_code`;

https
.get(url, (res) => {
let raw = '';
res.on('data', (chunk) => (raw += chunk));
res.on('end', () => {
try {
resolve(JSON.parse(raw));
} catch (err) {
reject(err);
}
});
})
.on('error', reject);
});
}

两个容易忽略的点:

  • WX_APPIDWX_SECRET 一定要通过云函数环境变量注入。AppSecret 一旦进了仓库,重置流程比配环境变量麻烦得多,不要图省事硬编码。
  • createTicket 的第一个参数作为 customUserId,微信 openid 通常是 28 位字母+数字,满足「4-32 位、字母/数字/部分符号」的规则,可以直接传。

第三步:部署云函数,开启 HTTP 访问

用 Cloudbase CLI 部署:

tcb login
tcb fn deploy getLoginTicket --dir ./cloudfunctions/getLoginTicket -e your-env-id

部署完到控制台完成三件事:

  1. 云函数 → getLoginTicket → 触发方式,添加「HTTP 访问服务」触发,允许 POST。复制生成的访问 URL,形如 https://your-env.service.tcloudbase.com/getLoginTicket
  2. 云函数 → getLoginTicket → 环境变量,添加 WX_APPIDWX_SECRETTCB_ENV
  3. 身份认证 → 登录方式,确认「自定义登录」是「已启用」状态,并且确认当前下载的 tcb_custom_login.json 是最近一次生成的(每次重新生成会让旧私钥在 2 小时后失效,这个坑后面会再提一次)

第四步:小程序端初始化 SDK,完成登录

miniprogram/libs/cloudbase.js

import cloudbase from '@cloudbase/js-sdk';
import adapter from '@cloudbase/adapter-wx_mp';

// adapter 必须在 init 之前注册
cloudbase.useAdapters(adapter);

const app = cloudbase.init({
env: 'your-env-id',
});

export const auth = app.auth();
export default app;

miniprogram/libs/login.js

import { auth } from './cloudbase';

const TICKET_URL = 'https://your-env.service.tcloudbase.com/getLoginTicket';

export async function ensureLogin() {
if (auth.hasLoginState()) {
return auth.currentUser;
}

// 1. wx.login 拿 code
const { code } = await new Promise((resolve, reject) => {
wx.login({ success: resolve, fail: reject });
});

// 2. 调云函数的 HTTP 接口换 ticket
const ticket = await new Promise((resolve, reject) => {
wx.request({
url: TICKET_URL,
method: 'POST',
data: { code },
success: (res) => {
if (res.statusCode === 200 && res.data && res.data.ticket) {
resolve(res.data.ticket);
} else {
reject(new Error('获取 ticket 失败: ' + JSON.stringify(res.data)));
}
},
fail: reject,
});
});

// 3. 注入 ticket 并登录
await auth.setCustomSignFunc(() => Promise.resolve(ticket));
await auth.signInWithCustomTicket();

return auth.currentUser;
}

miniprogram/app.jsonLaunch 里调用:

import { ensureLogin } from './libs/login';

App({
async onLaunch() {
try {
const user = await ensureLogin();
console.log('[cloudbase] logged in as', user.uid);
this.globalData.user = user;
} catch (err) {
console.error('[cloudbase] login failed', err);
}
},
globalData: {
user: null,
},
});

运行验证

  1. 微信开发者工具 → 工具 → 构建 npm
  2. 点击右上角 编译,打开 Console 面板
  3. 预期输出:
[cloudbase] logged in as 138f4c8c-xxxx-xxxx-xxxx-xxxxxxxxxxxx

uid 是 Cloudbase 侧分配的全局唯一 ID,和 openid 不是一回事——openid 是微信侧的用户标识,uid 是 Cloudbase 账号的主键,后面数据库和云存储的 owner 字段用的都是 uid。

  1. 去 Cloudbase 控制台 → 身份认证 → 用户管理,应该能看到一条新记录,customUserId 字段等于这次的 openid。

常见错误

错误码 / 错误信息原因修复
40029wx.login 返回的 code 已被使用或已过期(code 只能换一次 session,有效期 5 分钟)每次登录都重新调 wx.login,不要缓存旧 code
45011jscode2session 调用太频繁(同 IP 达到上限)云函数里给 ticket 加一层缓存,key 用 openid,TTL 设成 5-10 分钟
INVALID_TICKET小程序端 signInWithCustomTicket 报错多半是 tcb_custom_login.json 不是最新那份。控制台每次重新生成私钥,旧私钥会在 2 小时后失效,重新下载并重新部署云函数即可
customUserId 格式错误createTicket 传入了空字符串或非法字符检查 jscode2session 返回值里 openid 字段是否存在,不存在说明前面已经有 errcode
require is not defined(小程序端)没执行「构建 npm」微信开发者工具 → 工具 → 构建 npm,然后重新编译

错误码定义参考 docs.cloudbase.net/error-code,微信侧错误码看 微信开放文档 / 错误码

相关文档

@next (v3) 版本的变更点

如果你想用 @cloudbase/js-sdk@3.3.2(挂在 npm @next tag 上),和上面的代码对比有两个差异:

  1. 小程序适配器已内置进主包,不再需要单独安装和 cloudbase.useAdapters(adapter) 这一步
  2. 其余代码(cloudbase.initauth.signInWithCustomTicket 等)保持一致

v3 目前还没从 @next 提升到 latest,稳定性不如 2.27.3,生产项目建议先稳 2.x,观察一段时间再迁。

下一步

  • 登录后读写云数据库:add-database-wechat-miniprogram(待补)
  • 登录相关错误排查:fix-auth-wechat-miniprogram
  • 从 Firebase Auth 迁过来:migrate-firebase-auth