微信小程序登录流程详解:wx.login、code 与后端 session
微信小程序登录流程不是调用一次 wx.login() 就结束。客户端拿到的是短期、一次性的 code;业务后端再用它向微信接口换取 openid 和 session_key,最后还要建立自己可续期、可失效的业务 session。
这篇文章按真实的前后端边界拆解整个过程,并给出小程序端与 Node.js 后端示例。开始前应先准备好 AppID,并确保 AppSecret 只存在于服务器环境变量中;如果环境还没配置,可以先看微信小程序账号、AppID 与开发者工具配置。项目里的登录请求封装建议放在 services/,目录职责可参考微信小程序项目目录结构详解。
一、微信小程序登录流程中的四种凭证
| 数据 | 由谁产生 | 应该保存在哪里 | 用途 |
|---|---|---|---|
code | wx.login() | 不长期保存,用完即弃 | 让业务后端临时换取微信侧身份信息 |
openid | code2Session | 业务数据库 | 标识当前小程序中的微信用户 |
session_key | code2Session | 仅业务后端 | 微信用户数据签名、解密等场景使用的会话密钥 |
业务 accessToken | 业务后端 | 客户端保存原始 token;服务端保存会话或 token 摘要 | 后续业务接口识别登录用户 |
最关键的区分是:code 不是用户 ID,也不是可以反复使用的访问令牌;session_key 不是业务 session;openid 可以关联用户记录,但单独把它传回服务器不能证明请求者就是该用户。
二、完整时序:客户端、业务后端与微信服务器如何配合
- 小程序调用
wx.login()获取临时登录凭证code。 - 小程序立即把
code发送给自己的业务后端。 - 业务后端携带 AppID、AppSecret 和
code调用微信code2Session。 - 微信返回
openid、session_key,符合条件时还会返回unionid。 - 业务后端按
openid查找或创建用户,并生成自己的随机业务 token。 - 小程序保存业务 token,后续请求只携带这个 token,不携带 AppSecret 或
session_key。

按当前微信开放文档,wx.login() 返回的 code 有效期为五分钟,并且只能使用一次。因此不应预取一批 code,也不要在 code2Session 失败后无限重试同一个 code。
三、小程序端:调用 wx.login 并把 code 交给后端
客户端只负责取得 code 和调用自己的登录接口。下面把登录封装为 Promise,页面或 app.js 可以按业务需要调用;示例中的域名必须是已配置的 HTTPS 合法域名。
// services/auth.js
function requestLogin(code) {
return new Promise((resolve, reject) => {
wx.request({
url: 'https://api.example.com/auth/wechat/login',
method: 'POST',
data: { code },
success: ({ statusCode, data }) => {
if (statusCode === 200 && data.accessToken) {
resolve(data)
return
}
reject(new Error(data.message || '登录接口返回异常'))
},
fail: reject
})
})
}
export function login() {
return new Promise((resolve, reject) => {
wx.login({
timeout: 8000,
success: (result) => {
if (!result.code) {
reject(new Error('wx.login 未返回 code'))
return
}
requestLogin(result.code).then(resolve).catch(reject)
},
fail: reject
})
})
}
拿到后端返回的 accessToken 后,可以写入本地存储,并由统一的请求封装添加到请求头。不要把 AppSecret 写进 project.config.json、utils、云端可下载的配置文件或任何会进入小程序包的代码。
四、后端:用 code2Session 换取 openid 与 session_key
code2Session 是服务端 HTTPS GET 接口,请求参数包含 appid、secret、js_code 和固定值 authorization_code。下面是 Node.js 18+ 与 Express 风格的核心实现;users 和 sessionStore 代表你项目中的数据库适配器。
import crypto from 'node:crypto'
const WX_SESSION_URL = 'https://api.weixin.qq.com/sns/jscode2session'
const SESSION_TTL_SECONDS = 2 * 60 * 60 // 业务示例值,不是微信规定
function sha256(value) {
return crypto.createHash('sha256').update(value).digest('hex')
}
app.post('/auth/wechat/login', async (req, res) => {
const code = String(req.body?.code || '').trim()
if (!code) {
return res.status(400).json({ message: '缺少 code' })
}
const query = new URLSearchParams({
appid: process.env.WX_APPID,
secret: process.env.WX_APPSECRET,
js_code: code,
grant_type: 'authorization_code'
})
try {
const wxResponse = await fetch(`${WX_SESSION_URL}?${query}`, {
signal: AbortSignal.timeout(8000)
})
const wxData = await wxResponse.json()
if (wxData.errcode || !wxData.openid || !wxData.session_key) {
console.warn('code2Session failed', {
errcode: wxData.errcode,
errmsg: wxData.errmsg
})
return res.status(401).json({ message: '微信登录凭证无效,请重试' })
}
const user = await users.findOrCreateByOpenId(wxData.openid)
const rawToken = crypto.randomBytes(32).toString('base64url')
await sessionStore.set(
sha256(rawToken),
{
userId: user.id,
openid: wxData.openid,
sessionKey: wxData.session_key
},
SESSION_TTL_SECONDS
)
return res.json({
accessToken: rawToken,
expiresIn: SESSION_TTL_SECONDS,
user: { id: user.id }
})
} catch (error) {
console.error('wechat login request failed', error.message)
return res.status(502).json({ message: '登录服务暂时不可用' })
}
})
这个示例没有把 session_key 或 AppSecret 写进日志,也没有把它们返回给客户端。微信接口的业务失败会通过 errcode 和 errmsg 表达,因此不能只检查 HTTP 状态码。生产环境还应校验环境变量、限制登录接口频率,并为微信请求设置超时。
五、为什么后端还要建立自己的业务 session
微信返回的是微信侧身份与会话材料,不会替你的应用管理会员状态、角色、封禁、退出和多设备登录。业务后端仍要生成自己的 token,并把它映射到内部用户 ID。这样才能在不暴露微信密钥的情况下完成鉴权,也能独立控制有效期与撤销。
| 方案 | 适合场景 | 需要注意 |
|---|---|---|
| 随机不透明 token + Redis/数据库 session | 希望即时退出、封禁或集中控制会话 | 服务端保存 token 摘要,设置 TTL,并处理存储故障 |
| 短期 JWT + 刷新令牌 | 多服务读取身份,能承担更复杂的密钥与撤销设计 | 不要把长期敏感数据塞入 JWT;仍需设计刷新和吊销机制 |
对多数中小型小程序,不透明随机 token 更容易审计:客户端拿原始值,服务端只存其哈希;退出登录时删除会话记录即可。示例里的两小时只是业务演示值,应按风险、使用频率和续期策略自行确定。
六、后续接口如何校验业务 token
小程序后续请求可以把 token 放在 Authorization 请求头中。后端提取原始 token,计算同样的哈希并查询 session;查不到、过期或用户已被禁用时返回 401,而不是重新信任客户端提交的 openid。
async function requireLogin(req, res, next) {
const value = req.get('authorization') || ''
const match = value.match(/^Bearers+(.+)$/i)
if (!match) {
return res.status(401).json({ message: '未登录' })
}
const session = await sessionStore.get(sha256(match[1]))
if (!session) {
return res.status(401).json({ message: '登录已过期' })
}
req.auth = { userId: session.userId }
next()
}
客户端收到 401 后,应清理旧业务 token,再重新执行一次完整登录;不要在请求拦截器中并发触发多个 wx.login()。实际项目可以用一个共享 Promise 合并同时发生的刷新请求,避免多个 code 交叉覆盖登录结果。
七、openid、UnionID 和业务用户 ID 怎么对应
openid 是用户在当前小程序中的标识。只有小程序已绑定微信开放平台账号且满足返回条件时,接口才会提供 unionid;它用于同一开放平台主体下的跨应用身份关联,不能假设每次登录都有。
| 字段 | 建议唯一约束 | 使用方式 |
|---|---|---|
openid | appid + openid | 定位某一个小程序中的微信身份 |
unionid | 有值时按开放平台范围设计 | 跨小程序、公众号或应用合并身份前先核对绑定关系 |
内部 user_id | 业务数据库主键 | 订单、权限、资料和 session 都关联它 |
数据库不要只把 openid 当作全平台唯一值。更稳妥的结构是保留内部 user_id,再维护微信身份表,至少记录对应的 AppID 与 openid。以后接入第二个小程序或公众号时,不必重构所有业务表。
八、wx.checkSession 能检查什么,不能检查什么
wx.checkSession() 检查的是微信侧登录态是否过期,也就是开发者服务器保存的 session_key 是否仍可能有效。它不能验证你自己的业务 accessToken,也不能代替业务后端的鉴权。
- 业务 token 仍有效,但
wx.checkSession()失败:普通业务接口仍可按你的策略工作;需要解密微信数据时重新登录。 wx.checkSession()成功,但业务 token 已过期:仍要重新向业务后端建立 session。- 用户主动退出:清理客户端 token,并让后端删除或吊销对应业务 session。
九、code2Session 常见错误怎么排查
40029,code 无效:确认没有重复使用同一个 code,AppID 与 AppSecret 属于同一个小程序,失败后重新调用wx.login()获取新 code。45011,调用过于频繁:不要立即无限重试;合并并发登录,并在稍后按退避策略重试。40226,登录被拦截:这是高风险用户拦截场景,应保留errcode与请求链路 ID,按官方安全方案处理,不能通过前端反复调用绕过。- 后端偶发超时:为微信请求设置超时,区分网络失败与业务错误,并避免在日志里输出带 AppSecret 的完整请求 URL。
- 开发工具正常、真机失败:检查业务接口 HTTPS 证书、合法域名、环境配置和真机网络;不要只依赖开发工具里的“不校验合法域名”选项。
十、七个常见的登录实现错误
- 把 AppSecret 写进小程序代码,认为混淆后就安全;
- 把
session_key返回客户端,或完整写入应用日志; - 把
wx.login()成功等同于业务用户已经登录; - 把
openid当成客户端可以直接提交的登录凭证; - 重复使用、缓存或排队消费已经失效的 code;
- 只检查微信接口的 HTTP 状态,不检查
errcode; - 多个请求同时触发登录刷新,导致新旧 token 相互覆盖。
十一、上线前安全检查清单
- AppSecret 只存在于后端密钥管理或环境变量中;
- 客户端拿到 code 后立即发送,不长期缓存、不重复使用;
- 后端检查
errcode、openid与session_key,并设置请求超时; session_key不下发客户端,不进入普通日志;- 业务 token 使用足够随机值,服务端保存摘要并设置有效期;
- 登录、刷新和失败重试都有限频与并发合并;
- 用真机验证 HTTPS、合法域名、弱网、过期 token 和退出登录;
- 数据库以内部用户 ID 关联业务数据,微信身份单独建映射。
把微信侧身份交换和自己的业务 session 分开之后,登录问题会清晰很多:前者解决“这个微信用户是谁”,后者解决“这次业务请求是否仍被允许”。排查时也可以沿着 code、code2Session、用户映射和业务 token 四个节点逐段定位,而不是在客户端反复调用 wx.login()。
官方资料:微信开放文档《小程序登录》、微信开放文档《wx.login》、微信开放文档《小程序登录凭证校验》(访问日期:2026 年 8 月 24 日)




