Skip to main content

Add Authentication to a Web React Project with CloudBase SDK

In one sentence: Use @cloudbase/js-sdk in 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 a react-router-dom Route 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-sdk server tokens, outside the scope of @cloudbase/js-sdk)

Prerequisites

DependencyVersion
Node.js18
@cloudbase/js-sdk2.27.3
react / react-dom^18.2.0
react-router-dom^6.x
Build toolVite ^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 Verification Code region restriction

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 verificationInfo returned by getVerification must be passed as-is to signInWithSms — 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:

  1. Frontend calls genProviderRedirectUri to generate a WeChat authorization URL and redirects to it
  2. User scans the QR code and authorizes; WeChat redirects back to provider_redirect_uri with code and state in the query string
  3. The callback page uses grantProviderToken to exchange the code for a provider_token, then calls signInWithProvider to 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_uri must match the "Authorization Callback Domain" configured in the WeChat Open Platform "Website Application" settings (only the domain is compared, not the path)
  • state is 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_found error. They need to follow the two-step flow: "register with another method → call bindWithProvider to 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

  1. Run npm run dev to start Vite; open the browser and navigate to / — it should redirect to /login
  2. 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 /
  3. In Console → Authentication → User Management, the logged-in user record should be visible, with type "Phone"
  4. After signing out, log in again with the same phone number — it should be the same uid (no duplicate user created)
  5. Email flow: try with an email address; it should create and log in successfully
  6. WeChat flow: click "WeChat QR Login", scan the QR code on the WeChat Open Platform page, and after authorization the callback at /login/wechat-callback should complete the login

Common Errors

Error Code / SymptomCauseFix
SMS send returns "region not supported"CloudBase environment is not in ShanghaiSMS login only supports Shanghai region; switch to a Shanghai environment or use another login method
phone_number format invalidMissing +86 country codePass Chinese mainland numbers as +86 13800000000
30-second resend limit hit frequently1 SMS per number per 30 secondsImplement a 60-second client-side countdown
WeChat QR callback at /login/wechat-callback errorsprovider_redirect_uri does not match the "Authorization Callback Domain" on the Open PlatformThe Open Platform only checks the domain; confirm your location.origin domain is added
signInWithProvider returns not_foundThis WeChat account has not been linked to a CloudBase userFollow the "register with phone/email → bindWithProvider → scan again" flow
Still shows as logged in after sign outsignOut is async; frontend setState not triggeredListen for the sign_out event from onLoginStateChanged and clear user in Context
Cross-origin / CORS errorCloudBase Web SDK targets Tencent domains by default — no CORS issue; check if you are proxying the SDK requestsDo not proxy SDK requests; let the browser call Tencent domains directly
Login state lost after token expiredOffline 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.

Next Steps