Add Authentication to a Web React Project with CloudBase SDK
In one sentence: Use
@cloudbase/js-sdkin a Vite + React 18 project to integrate CloudBase Authentication — no UI library dependency, write your own components. Covers three login methods: SMS Verification Code / email verification code / WeChat Open Platform QR Login, with persistent login state and areact-router-domRoute Guard.Estimated time: 50 minutes | Difficulty: Advanced
Applicable Scenarios
- Applicable: Web React projects (Next.js / Vite / CRA), wanting to integrate the CloudBase user system
- Applicable: Already have a WeChat Mini Program using add-auth-wechat-miniprogram, and want the web side to share the same user table (same CloudBase environment auth is sufficient)
- Not applicable: Using a pre-built UI package like
@cloudbase/login-ui-react(this recipe does not use such packages; pure SDK with custom components gives better control) - Not applicable: Server-side rendering scenarios where login state is needed on the Node.js side (that requires
@cloudbase/node-sdkserver tokens, outside the scope of@cloudbase/js-sdk)
Prerequisites
| Dependency | Version |
|---|---|
| Node.js | ≥ 18 |
@cloudbase/js-sdk | 2.27.3 |
react / react-dom | ^18.2.0 |
react-router-dom | ^6.x |
| Build tool | Vite ^5.x / Next.js / CRA — your choice |
Also required:
- 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"
- "Email Verification Code Login" (enabling "Built-in Email Sending" is the fastest zero-config option)
- "WeChat Open Platform Login" — enter the AppID and AppSecret from a website application registered on WeChat Open Platform
SMS login can only be enabled for the Shanghai region in Console. CloudBase defaults to Shanghai when region is not passed to init; Guangzhou / Beijing environments cannot use SMS.
Step 1: Initialize the SDK
npm i @cloudbase/js-sdk react-router-dom
src/cloudbase.ts:
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', // Web login state persists for 30 days; only cleared by explicit signOut
});
@cloudbase/js-sdk@2.x on Web only supports persistence: 'local'. The session / none options from 1.x are no longer supported.
Step 2: Login state Context
src/auth-context.tsx:
import {
createContext,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import { auth } from './cloudbase';
type LoginState = {
uid: string;
customUserId?: string;
loginType?: string;
} | null;
const AuthContext = createContext<{
user: LoginState;
loading: boolean;
}>({
user: null,
loading: true,
});
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<LoginState>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// hasLoginState is a synchronous localStorage read; check it on init
const cached = auth.hasLoginState();
if (cached) {
setUser(auth.currentUser as any);
}
setLoading(false);
// Listen for login state changes
const handler = (params: any) => {
const eventType = params?.data?.eventType;
switch (eventType) {
case 'sign_in':
setUser(auth.currentUser as any);
break;
case 'sign_out':
case 'credentials_error':
setUser(null);
break;
}
};
auth.onLoginStateChanged(handler);
// Note: onLoginStateChanged currently has no explicit "remove listener";
// no manual cleanup is needed on unmount
}, []);
const value = useMemo(() => ({ user, loading }), [user, loading]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export const useAuth = () => useContext(AuthContext);
Step 3: SMS Verification Code login
SMS login is a two-step process: getVerification to get the verificationInfo, then signInWithSms after the user enters the code.
src/login-sms.tsx:
import { useState } from 'react';
import { auth } from './cloudbase';
export function LoginBySms() {
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);
// 60s countdown
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 () => {
setErr('');
try {
await auth.signInWithSms({
verificationInfo,
verificationCode: code,
phoneNum: phone,
});
// Login successful; onLoginStateChanged triggers Context update
} catch (e: any) {
setErr(e.message || 'Login failed');
}
};
return (
<form onSubmit={(e) => { e.preventDefault(); 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:
- Phone numbers must include the country code. The CloudBase API requires the format
+86 13800000000(the space is optional; verify against the current SDK's accepted format) - The
verificationInforeturned bygetVerificationmust be passed as-is tosignInWithSms— it contains a one-time token - Only 1 SMS can be sent per number per 30 seconds; the default daily limit is 30 messages/day. This can be adjusted in Console → "Authentication → Login Methods → SMS Verification Code"
Step 4: Email verification code login
The interface mirrors SMS — only the getVerification parameter changes to email, and login uses signInWithEmail.
src/login-email.tsx:
import { useState } from 'react';
import { auth } from './cloudbase';
export function LoginByEmail() {
const [email, setEmail] = useState('');
const [code, setCode] = useState('');
const [verificationInfo, setVerificationInfo] = useState<any>(null);
const [err, setErr] = useState('');
const sendCode = async () => {
setErr('');
try {
const info = await auth.getVerification({ email });
setVerificationInfo(info);
} catch (e: any) {
setErr(e.message);
}
};
const submit = async () => {
setErr('');
try {
await auth.signInWithEmail({
verificationInfo,
verificationCode: code,
email,
});
} catch (e: any) {
setErr(e.message);
}
};
return (
<form onSubmit={(e) => { e.preventDefault(); submit(); }}>
<input
placeholder="email@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="button" onClick={sendCode}>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>
);
}
In Console's "Email Verification Code" panel, it's recommended to enable "Built-in Email Sending" — no SMTP configuration needed, active within 1 minute (see email-login).
Step 5: WeChat Open Platform QR Login
WeChat QR Login on the web uses an OAuth redirect flow — not signInWithCustomTicket (that is for in-Mini-Program flows). Three steps:
- Frontend calls
genProviderRedirectUrito generate a WeChat authorization URL and redirects to it - User scans the QR code and authorizes; WeChat redirects back to
provider_redirect_uriwithcodeandstatein the query string - The callback page uses
grantProviderTokento exchange thecodefor aprovider_token, then callssignInWithProviderto log in
src/login-wechat.tsx:
import { useEffect, useState } from 'react';
import { auth } from './cloudbase';
export function LoginByWechat() {
// Entry: redirect to the WeChat authorization page
const startLogin = async () => {
const state = Math.random().toString(36).slice(2);
sessionStorage.setItem('wx_state', state);
const { uri } = await auth.genProviderRedirectUri({
provider_id: 'wx_open',
provider_redirect_uri: `${location.origin}/login/wechat-callback`,
state,
});
location.href = uri;
};
return <button onClick={startLogin}>WeChat QR Login</button>;
}
// Callback page: mounted at the /login/wechat-callback route
export function WechatCallback() {
const [err, setErr] = useState('');
useEffect(() => {
handle();
}, []);
async function handle() {
const params = new URLSearchParams(location.search);
const code = params.get('code');
const state = params.get('state');
// Validate state to prevent CSRF
const expected = sessionStorage.getItem('wx_state');
if (!code || !state || state !== expected) {
setErr('State validation failed');
return;
}
try {
const { provider_token } = await auth.grantProviderToken({
provider_id: 'wx_open',
provider_redirect_uri: `${location.origin}/login/wechat-callback`,
provider_code: code,
});
try {
await auth.signInWithProvider({ provider_token });
// Success — redirect to home page
location.href = '/';
} catch (e: any) {
if (e.error === 'not_found') {
// First-time WeChat login — user needs to register / link first
// Simple approach: redirect to a guide page to register with another method,
// then call bindWithProvider
setErr('Account not linked. Please register with another method and link your WeChat account.');
} else {
throw e;
}
}
} catch (e: any) {
setErr(e.message);
}
}
return <div>{err ? <div style={{ color: 'red' }}>{err}</div> : 'Logging in...'}</div>;
}
Key points:
provider_redirect_urimust match the "Authorization Callback Domain" configured in the WeChat Open Platform "Website Application" settings (only the domain is compared, not the path)stateis used to prevent CSRF — save it before login and validate it in the callback- New users logging in with WeChat for the first time will receive a
not_founderror. They need to follow the two-step flow: "register with another method → callbindWithProviderto link → scan QR code again". See wechat-login for the registration flow section
Step 6: Route Guard
Using react-router-dom v6:
src/app.tsx:
import {
BrowserRouter,
Routes,
Route,
Navigate,
useLocation,
} from 'react-router-dom';
import { AuthProvider, useAuth } from './auth-context';
import { LoginBySms } from './login-sms';
import { LoginByEmail } from './login-email';
import { LoginByWechat, WechatCallback } from './login-wechat';
function RequireAuth({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth();
const location = useLocation();
if (loading) return <div>Loading...</div>;
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return <>{children}</>;
}
export function App() {
return (
<AuthProvider>
<BrowserRouter>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/login/wechat-callback" element={<WechatCallback />} />
<Route
path="/"
element={
<RequireAuth>
<Home />
</RequireAuth>
}
/>
</Routes>
</BrowserRouter>
</AuthProvider>
);
}
function LoginPage() {
return (
<div>
<h2>Login</h2>
<LoginBySms />
<hr />
<LoginByEmail />
<hr />
<LoginByWechat />
</div>
);
}
<RequireAuth> redirects unauthenticated users to /login; login state is maintained in <AuthProvider>.
Step 7: Sign out
import { auth } from './cloudbase';
async function handleSignOut() {
await auth.signOut();
// onLoginStateChanged fires sign_out; Context clears automatically,
// and the Route Guard redirects the user back to /login
}
signOut() clears the local login state. Token refresh is handled internally by the SDK — no manual renewal needed on the business side.
Verification
- Run
npm run devto start Vite; open the browser and navigate to/— it should redirect to/login - Use a real phone number for the SMS Verification Code flow; after receiving the code and logging in, there should be no Console errors; success redirects back to
/ - In Console → Authentication → User Management, the logged-in user record should be visible, with type "Phone"
- After signing out, log in again with the same phone number — it should be the same uid (no duplicate user created)
- Email flow: try with an email address; it should create and log in successfully
- WeChat flow: click "WeChat QR Login", scan the QR code on the WeChat Open Platform page, and after authorization the callback at
/login/wechat-callbackshould complete the login
Common Errors
| Error Code / Symptom | Cause | Fix |
|---|---|---|
| SMS send returns "region not supported" | CloudBase environment is not in Shanghai | SMS login only supports 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 |
| 30-second resend limit hit frequently | 1 SMS per number per 30 seconds | Implement a 60-second client-side countdown |
WeChat QR callback at /login/wechat-callback errors | provider_redirect_uri does not match the "Authorization Callback Domain" on the Open Platform | The Open Platform only checks the domain; confirm your location.origin domain is added |
signInWithProvider returns not_found | This WeChat account has not been linked to a CloudBase user | Follow the "register with phone/email → bindWithProvider → scan again" flow |
| Still shows as logged in after sign out | signOut is async; frontend setState not triggered | Listen for the sign_out event from onLoginStateChanged and clear user in Context |
| Cross-origin / CORS error | CloudBase Web SDK targets Tencent domains by default — no CORS issue; check if you are proxying the SDK requests | Do not proxy SDK requests; let the browser call Tencent domains directly |
Login state lost after token expired | Offline for too long (more than 30 days persistence) | Have the user go through the full login flow again |
For error code definitions, see error-code.
Related Documentation
- SDK Initialization —
cloudbase.init/auth()parameters - SMS Verification Code Login — Interface flow
- Email Verification Code Login — Built-in email sending / custom SMTP
- WeChat Open Platform Login — OAuth redirect + bind flow
- Web SDK auth Full API — All Auth.* signatures
- add-auth-wechat-miniprogram — Cross-reference: Mini Program authentication
Next Steps
- Read and write the database under the same user system: add-database-wechat-miniprogram (the database portion applies to Web as well)
- Add Realtime Notifications to the web side: add-realtime-notifications-database-watch
- Security Rules for multi-tenant scenarios: secure-database-multi-tenant-rules