Add CloudBase Authentication to Next.js
In one sentence: Use
@cloudbase/js-sdkin a Next.js 14+ App Router project to integrate SMS Verification Code / WeChat Open Platform QR Login, store login state in anhttpOnlycookie, read identity in Server Components viacookies(), perform lightweight validation in middleware running on Edge Runtime, and run heavy business logic in Route Handlers withruntime: 'nodejs'.Estimated time: 35 minutes | Difficulty: Advanced
Applicable Scenarios
- Applicable: Next.js 14 / 15 App Router full-stack applications where SSR pages need user identity for personalized rendering
- Applicable: React SPAs already using add-auth-web-with-cloudbase-sdk that want to migrate to Next.js for SSR + middleware interception
- Applicable: Using middleware for unauthenticated request interception / canary routing / A-B splits
- Not applicable: Pure Pages Router projects — go directly to the add-auth-web-with-cloudbase-sdk SPA template. Pages Router does not have the concept of Server Components or Server Actions
- Not applicable: Obtaining admin-level login state on the Node.js side (that requires server tokens from
@cloudbase/node-sdk, which is outside the scope of this recipe)
Prerequisites
| Dependency | Version |
|---|---|
| Node.js | ≥ 18.18.0 |
next | 14.2.x or 15.x |
@cloudbase/js-sdk | 2.27.3 (or @cloudbase/js-sdk@next, i.e. 3.3.x) |
react / react-dom | ^18.3.0 (Next.js 15 uses ^19.0.0) |
External dependencies:
- An active CloudBase environment ID, region set to Shanghai (SMS Verification Code login is only supported in the Shanghai region)
- In Console → Authentication → Login Methods, enable the methods you want:
- "SMS Verification Code Login"
- "WeChat Open Platform Login" — enter the AppID and AppSecret from a website application registered on WeChat Open Platform
Next.js 15 changed cookies() / headers() / params / searchParams to all be async — they must be await-ed before use. Next.js 14 keeps the synchronous API. This recipe is written for Next.js 15; for Next.js 14 simply replace await cookies() with cookies().
Step 1: Install @cloudbase/js-sdk in Your Next.js Project
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
Create lib/cloudbase.ts (client-only; the SDK uses crypto / localStorage):
'use client';
import cloudbase from '@cloudbase/js-sdk';
export const app = cloudbase.init({
env: 'your-env-id',
// Omitting region defaults to Shanghai; SMS login is only supported in Shanghai
});
export const auth = app.auth({ persistence: 'local' });
Step 2: Write the Login Page as a Client Component (SMS + WeChat QR)
app/login/page.tsx (marked 'use client' because it uses the 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('Phone number format should be +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 || 'Failed to send');
}
};
const submit = async (e: React.FormEvent) => {
e.preventDefault();
setErr('');
try {
await auth.signInWithSms({
verificationInfo,
verificationCode: code,
phoneNum: phone,
});
// Login successful — write access token to httpOnly cookie
const loginState = await auth.getLoginState();
if (!loginState) throw new Error('Login state is empty');
await setSessionCookie(loginState.accessToken);
router.push('/');
} catch (e: any) {
setErr(e.message || 'Login failed');
}
};
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 ? `Resend in ${countdown}s` : 'Get verification code'}
</button>
<input
placeholder="6-digit code"
value={code}
onChange={(e) => setCode(e.target.value)}
/>
<button type="submit">Login</button>
{err && <div style={{ color: 'red' }}>{err}</div>}
</form>
);
}
Key points:
auth.signInWithSmsfollows the two-step flowgetVerification→signInWithSms;verificationInfois the object returned bygetVerificationand must be passed back as-is- After a successful client-side login, the
accessTokenis written to anhttpOnlycookie via the Server ActionsetSessionCookie, making it accessible to Server Components and middleware - WeChat QR Login uses an OAuth redirect flow. The process is the same as in add-auth-web-with-cloudbase-sdk Step 5; in Next.js simply swap the callback page for
app/login/wechat-callback/page.tsx— the logic is unchanged
Step 3: Store Login State in an httpOnly Cookie (Server Action)
app/login/actions.ts (marked '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 days, aligned with SDK persistence: 'local'
});
}
export async function clearSessionCookie() {
const cookieStore = await cookies();
cookieStore.delete('cloudbase_session');
}
Key points:
httpOnly: trueprevents JavaScript from reading the cookie, protecting the token from XSSsameSite: 'lax'is the recommended default; do not use'strict'— cross-site redirects (e.g. WeChat QR callback) will not send the cookiesecure: trueis required in production; useprocess.env.NODE_ENV === 'production'so that local HTTP development still works- Server Components cannot directly
set/deletecookies — they can only read. Write operations must be placed in a Server Action or Route Handler
Step 4: Read Identity in a Server Component via cookies()
app/page.tsx (a Server Component by default — do not add 'use client'):
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
export default async function HomePage() {
const cookieStore = await cookies(); // must await in Next.js 15
const accessToken = cookieStore.get('cloudbase_session')?.value;
if (!accessToken) {
redirect('/login');
}
// If you only need to display "logged in" status, having the cookie is sufficient.
// To fetch detailed user info, call the CloudBase server-side API via a Route Handler,
// or write the uid into a separate cookie alongside the access token.
return (
<div>
<h1>Welcome</h1>
<p>You are logged in (token length: {accessToken.length})</p>
</div>
);
}
If you need uid, phone number, or other user details inside a Server Component, there are two approaches:
- At login time in Step 2, write a second
cloudbase_uidcookie in addition toaccessToken; the Server Component reads it directly - Have the Server Component call an internal Route Handler (
runtime: 'nodejs') that exchanges theaccessTokenfor detailed user info via the CloudBase server-side API (see Step 6)
Step 5: Intercept Unauthenticated Requests with middleware.ts
middleware.ts (place in the project root, not under app/):
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const session = request.cookies.get('cloudbase_session');
const { pathname } = request.nextUrl;
// Redirect logged-in users away from /login back to home
if (session && pathname.startsWith('/login')) {
return NextResponse.redirect(new URL('/', request.url));
}
// Redirect unauthenticated users trying to access protected routes to /login
if (!session && !pathname.startsWith('/login')) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('from', pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!login|_next/static|_next/image|favicon.ico|api/public).*)'],
};
Key points:
- middleware runs on Edge Runtime.
@cloudbase/js-sdkuses Node.js APIs (crypto,fs) and cannot beimport-ed inside middleware - middleware only does lightweight validation: whether the cookie exists and whether its format is valid. Whether the token is actually valid, whether it has expired, and which uid it belongs to should be handled in a Server Component or Route Handler
- The
matcheruses a negative lookahead(?!...)to exclude paths that do not need interception; omitting_next/staticcauses initial-load chunks to be redirected as well, resulting in a blank page - Redirecting logged-in users away from
/loginprevents the UX loop of "clicking login when already logged in"
Step 6: Call the CloudBase API from a Server Action (Write Data)
When business logic needs to use the accessToken to call CloudBase server-side APIs (read database / invoke cloud functions), place it in a Server Action or Route Handler with runtime: 'nodejs'.
app/api/profile/route.ts:
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';
// Required: force Node Runtime — @cloudbase/js-sdk cannot run on Edge
export const runtime = 'nodejs';
export async function GET() {
const cookieStore = await cookies();
const accessToken = cookieStore.get('cloudbase_session')?.value;
if (!accessToken) {
return NextResponse.json({ error: 'unauthenticated' }, { status: 401 });
}
// Inside Node Runtime, you can import and use the SDK / call HTTP normally.
// Use accessToken to call the CloudBase server-side OpenAPI, or use @cloudbase/node-sdk.
// This example only demonstrates forwarding the session check.
return NextResponse.json({ ok: true, hasSession: true });
}
Key points:
- Any server-side code that
imports@cloudbase/js-sdkor@cloudbase/node-sdkmust declareexport const runtime = 'nodejs' - The default Route Handler in Next.js inherits the runtime from its parent
route.tsconfig — an explicit declaration is the most reliable approach
Sign Out
'use client';
import { auth } from '@/lib/cloudbase';
import { clearSessionCookie } from '@/app/login/actions';
import { useRouter } from 'next/navigation';
export function SignOutButton() {
const router = useRouter();
const handleSignOut = async () => {
await auth.signOut(); // Clear the SDK's local login state
await clearSessionCookie(); // Server Action deletes the cookie
router.push('/login');
};
return <button onClick={handleSignOut}>Sign Out</button>;
}
signOut() clears the client-side SDK state; clearSessionCookie() clears the server-side cookie. Both steps are required — skipping either causes the client to appear logged out while SSR still shows the authenticated state.
Verification
- Run
npm run devand openhttp://localhost:3000in a browser — middleware should redirect you to/login?from=/ - Use a real phone number for the SMS Verification Code flow; after a successful login you should be redirected back to
/, where the Server Component renders the "Welcome" page - In Console → Authentication → User Management, the user record for this login should be visible, with type "Phone"
- In browser DevTools → Application → Cookies, the
cloudbase_sessioncookie should be present with theHttpOnlycolumn checked;document.cookiein the Console should not show it - Refresh the page (F5) — the Server Component should still recognize the login state and not redirect back to
/login(SSR reads the cookie) - Click Sign Out — the cookie is cleared; after a refresh you are redirected back to
/login
Common Errors
| Error / Symptom | Cause | Fix |
|---|---|---|
cookies().get is not a function or Property 'get' does not exist | Next.js 15 made cookies() async; calling it directly returns a Promise | Change to const cookieStore = await cookies(); cookieStore.get(...) |
import cloudbase from '@cloudbase/js-sdk' in middleware throws crypto is not defined or Module not found: Can't resolve 'fs' | middleware runs on Edge Runtime; the SDK uses Node.js APIs | Do not import the SDK in middleware — only read the cookie for existence checks; move real validation to a Route Handler with runtime: 'nodejs' |
cookieStore.set(...) in a Server Component throws Cookies can only be modified in a Server Action or Route Handler | The response headers are already sent during RSC streaming | Move set / delete operations into a 'use server' Server Action |
Adding sameSite: 'strict' to cookieStore.set causes the WeChat QR callback to not send the cookie | Strict mode does not send cookies on cross-site redirects | Change to sameSite: 'lax' (recommended default) |
Setting matcher: '/' in middleware leaves /about unprotected | A single string only matches / exactly, not as a prefix | Use the negative lookahead pattern ['/((?!login|_next).*)'], or use multiple rules |
| SMS send returns "region not supported" | CloudBase environment is not in Shanghai | SMS login only supports the Shanghai region; switch to a Shanghai environment or use another login method |
phone_number format invalid | Missing +86 country code | Pass Chinese mainland numbers as +86 13800000000 |
| Refreshing the page after login redirects back to /login | path: '/' was omitted in cookieStore.set, so the cookie only applies to the current path | Explicitly add path: '/' to cookieStore.set |
| Cookie not working after deploying to Vercel / CloudBase Hosting | secure: false in production causes the browser to reject the cookie, or secure: true with an HTTP site | Production must use HTTPS + secure: true; use secure: process.env.NODE_ENV === 'production' for local HTTP |
For error code definitions, see error-code.
Related Documentation
- add-auth-web-with-cloudbase-sdk — Authentication for Vite + React SPA; use this for Pages Router / pure SPA
- add-auth-uniapp — Unified authentication for multi-platform (H5 / Mini Program / App)
- SDK Initialization —
cloudbase.init/auth()parameters - SMS Verification Code Login —
getVerification/signInWithSmsinterface flow - WeChat Open Platform Login — OAuth redirect + bind flow
- Web SDK auth Full API — All
Auth.*signatures - error-code — Error code definitions