在 Next.js 中接入 CloudBase 用户认证
一句话定义:用
@cloudbase/js-sdk在 Next.js 14+ App Router 里做手机短信验证码 / 微信开放平台扫码登录,登录态用httpOnlycookie 存,Server Component 通过cookies()拿身份,middleware 在 Edge Runtime 做轻校验拦未登录请求,重业务在runtime: 'nodejs'的 Route Handler 里跑。预计耗时:35 分钟 | 难度:进阶
适用场景
- 适用:Next.js 14 / 15 App Router 全栈应用,SSR 页面要拿用户身份做个性化渲染
- 适用:已经用 add-auth-web-with-cloudbase-sdk 的 React SPA,想迁到 Next.js 拿到 SSR + middleware 拦截能力
- 适用:用 middleware 做未登录拦截 / 灰度路由 / A-B 分流
- 不适用:纯 Pages Router 项目,直接走 add-auth-web-with-cloudbase-sdk 的 SPA 模板即可,Pages Router 没有 Server Component / Server Action 的概念
- 不适用:在 Node.js 端拿 admin 级登录态(那要走
@cloudbase/node-sdk的 server token,不是本篇范畴)
环境要求
| 依赖 | 版本 |
|---|---|
| Node.js | ≥ 18.18.0 |
next | 14.2.x 或 15.x |
@cloudbase/js-sdk | 3.3.x(latest)或 @cloudbase/js-sdk@v2 即 2.28.x |
react / react-dom | ^18.3.0(Next.js 15 用 ^19.0.0) |
外部依赖:
- 已创建的 CloudBase 环境 ID,地域选「上海」(短信验证码登录仅支持上海地域)
- 控制台 → 身份认证 → 登录方式,把要用的方式开启:
- 「短信验证码登录」
- 「微信开放平台登录」,填入 微信开放平台 网站应用的 AppID 和 AppSecret
Next.js 15 cookies() 是 async
Next.js 15 把 cookies() / headers() / params / searchParams 都改成了 async,必须 await 再用。Next.js 14 是 sync 的,沿用旧写法即可。本篇按 Next.js 15 写,Next.js 14 把 await cookies() 改成 cookies() 即可。
第一步:Next.js 项目装 @cloudbase/js-sdk
npx create-next-app@latest my-cloudbase-app --typescript --app --tailwind=false --eslint --src-dir=false --import-alias="@/*"
cd my-cloudbase-app
npm i @cloudbase/js-sdk
新建 lib/cloudbase.ts(纯客户端用,SDK 用了 crypto / localStorage):
"use client";
import cloudbase from "@cloudbase/js-sdk";
export const app = cloudbase.init({
env: "your-env-id",
// 不传 region 默认上海;短信登录只在上海支持
});
export const auth = app.auth({ persistence: "local" });
第二步:Client Component 写登录页(短信验证码 + 微信扫码)
app/login/page.tsx(标 'use client' 因为要用 SDK):
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { auth } from "@/lib/cloudbase";
import { setSessionCookie } from "./actions";
export default function LoginPage() {
const router = useRouter();
const [phone, setPhone] = useState("");
const [code, setCode] = useState("");
const [verificationInfo, setVerificationInfo] = useState<any>(null);
const [countdown, setCountdown] = useState(0);
const [err, setErr] = useState("");
const sendCode = async () => {
setErr("");
if (!/^\+86\s?\d{11}$/.test(phone)) {
setErr("手机号格式应为 +86 13800000000");
return;
}
try {
const info = await auth.getVerification({ phone_number: phone });
setVerificationInfo(info);
setCountdown(60);
const t = setInterval(() => {
setCountdown((c) => {
if (c <= 1) {
clearInterval(t);
return 0;
}
return c - 1;
});
}, 1000);
} catch (e: any) {
setErr(e.message || "发送失败");
}
};
const submit = async (e: React.FormEvent) => {
e.preventDefault();
setErr("");
try {
await auth.signInWithSms({
verificationInfo,
verificationCode: code,
phoneNum: phone,
});
// 登录成功,拿 access token 写到 httpOnly cookie
const loginState = await auth.getLoginState();
if (!loginState) throw new Error("登录态为空");
await setSessionCookie(loginState.accessToken);
router.push("/");
} catch (e: any) {
setErr(e.message || "登录失败");
}
};
return (
<form onSubmit={submit}>
<input
placeholder="+86 13800000000"
value={phone}
onChange={(e) => setPhone(e.target.value)}
/>
<button type="button" onClick={sendCode} disabled={countdown > 0}>
{countdown > 0 ? `${countdown}s 后重发` : "获取验证码"}
</button>
<input
placeholder="6 位验证码"
value={code}
onChange={(e) => setCode(e.target.value)}
/>
<button type="submit">登录</button>
{err && <div style={{ color: "red" }}>{err}</div>}
</form>
);
}
要点:
auth.signInWithSms走的是「getVerification→signInWithSms」两步,verificationInfo是getVerification返回的对象,原样传回去- 客户端登录成功后,通过 Server Action
setSessionCookie把accessToken写进httpOnlycookie,这样 Server Component 和 middleware 才能拿到 - 微信扫码登录走 OAuth 跳转,流程见 add-auth-web-with-cloudbase-sdk 第五步,在 Next.js 里只需把回调页换成
app/login/wechat-callback/page.tsx即可,逻辑不变
第三步:登录态用 httpOnly cookie 存(Server Action 写 cookie)
app/login/actions.ts(标 'use server'):
"use server";
import { cookies } from "next/headers";
export async function setSessionCookie(accessToken: string) {
const cookieStore = await cookies();
cookieStore.set("cloudbase_session", accessToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
maxAge: 60 * 60 * 24 * 30, // 30 天,和 SDK 的 persistence: 'local' 对齐
});
}
export async function clearSessionCookie() {
const cookieStore = await cookies();
cookieStore.delete("cloudbase_session");
}
要点:
httpOnly: true让 JS 读不到 cookie,XSS 拿不走 tokensameSite: 'lax'是默认推荐;不要写'strict',跨站跳转(比如微信扫码回调)就不发 cookie 了secure: true生产环境必开,本地开发改成process.env.NODE_ENV === 'production',本地 HTTP 也能用- Server Component 不能直接
set/deletecookie,只能读;写操作必须放在 Server Action 或 Route Handler 里