--- name: auth-web-cloudbase description: CloudBase Web Authentication Quick Guide for frontend integration after auth-tool has already been checked. Provides concise and practical Web authentication solutions with multiple login methods and complete user management. version: 2.23.6 alwaysApply: false --- ## Standalone Install Note If this environment only installed the current skill, start from the CloudBase main entry and use the published `cloudbase/references/...` paths for sibling skills. - CloudBase main entry: `https://cnb.cool/tencent/cloud/cloudbase/cloudbase-skills/-/git/raw/main/skills/cloudbase/SKILL.md` - Current skill raw source: `https://cnb.cool/tencent/cloud/cloudbase/cloudbase-skills/-/git/raw/main/skills/cloudbase/references/auth-web/SKILL.md` Keep local `references/...` paths for files that ship with the current skill directory. When this file points to a sibling skill such as `auth-tool` or `web-development`, use the standalone fallback URL shown next to that reference. ## Activation Contract ### Use this first when - The task is a CloudBase Web login, registration, session, or user profile flow built with `@cloudbase/js-sdk` and the auth provider setup has already been checked. ### Read before writing code if - The user needs a login page, auth modal, session handling, or protected Web route. Read `auth-tool` first to ensure providers are enabled, then return here for frontend integration. ### Then also read - `../auth-tool/SKILL.md` (standalone fallback: `https://cnb.cool/tencent/cloud/cloudbase/cloudbase-skills/-/git/raw/main/skills/cloudbase/references/auth-tool/SKILL.md`) for provider setup - `../web-development/SKILL.md` (standalone fallback: `https://cnb.cool/tencent/cloud/cloudbase/cloudbase-skills/-/git/raw/main/skills/cloudbase/references/web-development/SKILL.md`) for Web project structure and deployment ### Do not start here first when - The request is a Web auth flow but provider configuration has not been verified yet. - In that case, activate `auth-tool-cloudbase` before `auth-web-cloudbase`. ### Do NOT use for - Mini program auth, native App auth, or server-side auth setup. ### Common mistakes / gotchas - Skipping publishable key and provider checks. - Replacing built-in Web auth with cloud function login logic. - Reusing this flow in Flutter, React Native, or native iOS/Android code. - Creating a detached helper file with `auth.signUp` / `verifyOtp` but never wiring it into the existing form handlers, so the actual button clicks still do nothing. - Using `signInWithEmailAndPassword` or `signUpWithEmailAndPassword` for username-style accounts such as `admin` and `editor`. - Keeping the login or register account input as `type="email"` when the task explicitly says the account identifier is a plain username string. - Starting implementation before calling `queryAppAuth(action="getLoginConfig")` and enabling `usernamePassword` when it is still off. - **Writing `auth.signInWithPassword(...)` or `auth.signUp(...)` code without first confirming the provider is enabled via MCP.** Before writing any sign-in or sign-up code in the browser, call `queryAppAuth(action="listProviders")` to verify the target provider (e.g. `email`, `phone`, `usernamePassword`) has `On: "TRUE"`. For email-based sign-up (`auth.signUp({ email, password })`), additionally confirm SMTP is configured β€” otherwise the provider may throw `"provider email not found"` or similar errors. For username/password login, use `auth.signInWithPassword({ username, password })`; registration is best done through the management API (`manageAppAuth(action="createUser")`) or by confirming email provider readiness first. - **Treating `auth.getUser()` or deprecated `auth.getLoginState()` as proof of real login.** When the SDK is initialized with `accessKey`, the deprecated `getLoginState()` returns an object with a valid `uid` even without any login β€” causing route guards that check `!!loginState` or `!!uid` to incorrectly pass. The fix is to use `auth.getSession()` instead: it returns `data.session === undefined` when no real login has occurred. Only `!!data.session` from `getSession()` is a reliable authentication check. - **Copying old CloudBase auth snippets from training data.** Do not use `auth.getLoginState()`, `auth.hasLoginState()`, `auth.getCurrentUser()`, or `auth.toDefaultLoginPage()` as the default Web flow. Use the Supabase-like Web SDK v2 auth methods in this file and provider readiness from `auth-tool`. Note: anonymous login is now **disabled by default** for new environments and inactive existing environments. Always use `auth.getSession()` for auth guards. ## Overview **Prerequisites**: CloudBase environment ID (`env`) **Prerequisites**: CloudBase environment Region (`region`) --- ## Core Capabilities **Use Case**: Web frontend projects using `@cloudbase/js-sdk@2.24.0+` for user authentication **Key Benefits**: **Supabase-compatible Auth API** β€” all methods return `{ data, error }`, supports phone, email, anonymous (disabled by default), username/password, OAuth, and third-party login methods > πŸ“Œ **Supabase API Compatibility**: CloudBase Web SDK v2 auth module is designed with Supabase-like API ergonomics. If you are familiar with `supabase-js` auth patterns, the same mental model applies: > - All methods return `Promise<{ data, error }>` β€” always check `error` first > - `signInWithPassword`, `signInWithOtp`, `signUp`, `signOut`, `getSession`, `getUser` follow the same naming as Supabase > - `onAuthStateChange(callback)` provides reactive auth state observation (events: `INITIAL_SESSION`, `SIGNED_IN`, `SIGNED_OUT`, `TOKEN_REFRESHED`, `USER_UPDATED`, `PASSWORD_RECOVERY`, `BIND_IDENTITY`) > - Session management via `getSession()` / `refreshSession()` / `setSession()` mirrors Supabase patterns > > **Key differences from Supabase**: > - **OTP verification**: Supabase uses a standalone `auth.verifyOtp({ phone, token, type })` call; CloudBase returns `verifyOtp` as a callback on `data` β€” call `data.verifyOtp({ token })` from the `signInWithOtp` / `signUp` result > - **`accessKey`** replaces Supabase's `anonKey`; environment uses `env` + `region` instead of Supabase's `url` > - **`signInWithIdToken`** for direct third-party token login (similar to Supabase's same-named method) Use npm installation for modern Web projects. In React, Vue, Vite, and other bundler-based apps, install and import `@cloudbase/js-sdk` from the project dependencies instead of using a CDN script. ## Prerequisites - Automatically use `auth-tool-cloudbase` to check app-side auth readiness via `queryAppAuth` / `manageAppAuth`, then get the `publishable key` and configure login methods. - If `auth-tool-cloudbase` failed, let user go to `https://tcb.cloud.tencent.com/dev?envId={env}#/env/apikey` to get `publishable key` and `https://tcb.cloud.tencent.com/dev?envId={env}#/identity/login-manage` to set up login methods ### Parameter map - For username-style identifiers, the required precondition is `loginMethods.usernamePassword === true` from `queryAppAuth(action="getLoginConfig")`. If it is false, enable it with `manageAppAuth(action="patchLoginStrategy", patch={ usernamePassword: true })` before wiring frontend auth code. - If the conversation only provides an environment alias, nickname, or other shorthand, resolve it with `envQuery(action="list", alias=..., aliasExact=true)` first and use the returned canonical full `EnvId` for SDK init, console links, and generated config. Do not pass alias-like short forms directly into `cloudbase.init({ env })`. - Treat CloudBase Web Auth as **Supabase-like**, not β€œevery `supabase-js` auth example is valid unchanged” - When `queryAppAuth` / `manageAppAuth` returns `sdkStyle: "supabase-like"` and `sdkHints`, follow those method and parameter hints first - `auth.signInWithOtp({ phone })` and `auth.signUp({ phone })` use the phone number in a `phone` field, not `phone_number` - `auth.signInWithOtp({ email })` and `auth.signUp({ email })` use `email` - `auth.signInWithPassword({ username, password })` is the canonical Web login path for username/password accounts - Treat direct Web `auth.signUp({ username, password })` as conditional. Verify `sdkHints` and the installed SDK first; some versions only support `signUp` for OTP/provider-token flows and will not create username/password users. - If the task gives accounts like `admin`, `editor`, or another plain string without `@`, treat it as a username-style identifier rather than an email address - `verifyOtp({ token })` expects the SMS or email code in `token` - `accessKey` is the publishable key from `queryAppAuth` / `manageAppAuth` via `auth-tool-cloudbase`, not a secret key - **`accessKey` triggers automatic anonymous session creation** β€” the deprecated `auth.getLoginState()` returns an object with a valid `uid` even without explicit login, which misleads route guards into thinking the user is authenticated. Use `auth.getSession()` instead β€” it returns `data.session === undefined` when no real login has occurred, making auth checks straightforward and reliable. - Never set `accessKey` to `envId`, a username, or any placeholder string. If you do not have a real Publishable Key yet, do not fabricate one. - If the task mentions provider setup, stop and read `auth-tool-cloudbase` before writing frontend code ## Quick Start ```js // npm install @cloudbase/js-sdk import cloudbase from '@cloudbase/js-sdk' const app = cloudbase.init({ env: 'your-full-env-id', // Canonical full CloudBase environment ID resolved from envQuery or the console, not an alias or shorthand region: `region`, // CloudBase environment Region, default 'ap-shanghai' accessKey: 'publishable key', // required, get from auth-tool-cloudbase // ⚠️ With accessKey, the deprecated getLoginState() returns misleading auth data (uid) // even without login. Always use auth.getSession() β€” returns undefined when not logged in. auth: { detectSessionInUrl: true }, // required }) const auth = app.auth({ persistence: 'local' }) ``` If the current task has not retrieved a real Publishable Key, omit `accessKey` instead of inventing one. A wrong `accessKey` can break auth-state checks and protected-route behavior. --- ## Login Methods **1. Phone OTP (Recommended)** - Automatically use `auth-tool-cloudbase` to turn on `SMS Login` through `manageAppAuth` - Send the phone number to `auth.signInWithOtp({ phone, ... })`, then call the returned `verifyOtp({ token })`. - `signInWithOtp` can automatically create a new user if the user does not exist; control this via `shouldCreateUser` parameter (default `true`). ```js const { data, error } = await auth.signInWithOtp({ phone: '13800138000' }) const { data: loginData, error: loginError } = await data.verifyOtp({ token: '123456' }) ``` **2. Email OTP** - Automatically use `auth-tool-cloudbase` to turn on `Email Login` through `manageAppAuth` ```js const { data, error } = await auth.signInWithOtp({ email: 'user@example.com' }) const { data: loginData, error: loginError } = await data.verifyOtp({ token: '654321' }) ``` **3. Password** All auth methods return `{ data, error }`. Always check `error` first: ```js // Login β€” returns { data: { user, session }, error: null } on success const { data, error } = await auth.signInWithPassword({ username: 'test_user', password: 'pass123' }) if (error) { // Handle login failure (wrong password, user not found, provider not enabled) console.error('Login failed:', error.message) return false } // data.user.id is the uid; data.session contains the active session const uid = data.user.id // Also works with email or phone: // await auth.signInWithPassword({ email: 'user@example.com', password: 'pass123' }) // await auth.signInWithPassword({ phone: '13800138000', password: 'pass123' }) ``` **Checking login state (for route guards / auth checks):** ```js // Use auth.getSession() β€” NOT the deprecated getLoginState(). // // Why: getLoginState() returns an object with uid even when only accessKey is // present (no real login), causing route guards to incorrectly pass anonymous users. // getSession() returns data.session === undefined when no real login exists, // making the check reliable and simple. const { data, error } = await auth.getSession() if (!data?.session) { // No real login β€” redirect to sign-in page window.location.href = '/login' return } // Also reject anonymous sessions (when signInAnonymously() was called explicitly) if (data.session.user?.is_anonymous) { // Anonymous user β€” not allowed for protected routes window.location.href = '/login' return } // data.session contains: access_token, refresh_token, expires_in, user // data.session.user contains the authenticated user info const currentUser = data.session.user // Optional: further verify identity type from the session user if needed const hasVerifiedIdentity = Boolean( currentUser.phone_confirmed_at || currentUser.email_confirmed_at || currentUser.user_metadata?.username ) // ❌ Do NOT use auth.getLoginState() β€” it's deprecated and returns // misleading data (uid/loginState) even without real login // ❌ Do NOT use auth.getUser(), !!loginState, or !!loginState.uid as auth checks ``` **4. Registration** - For username-style account systems, verify whether direct username/password signup is supported before wiring the form - Username/password login can use plain identifiers such as `admin` or `editor`, but raw signup APIs may enforce stricter username patterns or be disabled - Do not switch to email OTP or phone OTP unless the task explicitly says the account identifier is an email address or phone number - If direct username signup is unsupported, create users through a backend or management API boundary; never put secret keys in browser code ```js // Username + Password login const login = await auth.signInWithPassword({ username: 'editor', password: 'editor123', }) // Email Otp // Use only when the task explicitly requires email addresses. // Email Otp const emailSignUp = await auth.signUp({ email: 'new@example.com', nickname: 'User' }) const emailVerifyResult = await emailSignUp.data.verifyOtp({ token: '123456' }) // Phone Otp // Use only when the task explicitly requires phone numbers. // Phone Otp const phoneSignUp = await auth.signUp({ phone: '13800138000', password: 'pass123', nickname: 'User' }) const phoneVerifyResult = await phoneSignUp.data.verifyOtp({ token: '123456' }) ``` When the project already has `handleSendCode` / `handleRegister` or similar UI handlers, wire the SDK calls there directly instead of leaving them commented out in `App.tsx`. For username-style account tasks: ```tsx const handleRegister = async () => { const { error } = await auth.signUp({ username, password, nickname: username, }) if (error) throw error } const handleLogin = async () => { const { data, error } = await auth.signInWithPassword({ username, password, }) if (error) throw error // Login succeeded β€” data.user.id is the uid return true } ``` Do not use email OTP or email-only helpers for these flows unless the task explicitly says the account identifier is an email address. The corresponding form field should stay `type="text"` rather than `type="email"` for username-style account identifiers. ```tsx const handleSendCode = async () => { try { const { data, error } = await auth.signUp({ phone, password: password || undefined, }) if (error) throw error verifyOtpRef.current = data.verifyOtp } catch (error) { console.error('Failed to send sign-up code', error) } } const handleRegister = async () => { try { if (!verifyOtpRef.current) throw new Error('Please send the code first') const { error } = await verifyOtpRef.current({ token: code }) if (error) throw error } catch (error) { console.error('Failed to complete sign-up', error) } } ``` **5. Anonymous** > ⚠️ **Anonymous login is disabled by default for new environments.** The SDK initialized with `accessKey` will automatically create an anonymous session regardless of this setting. Do not rely on `signInAnonymously()` for production flows β€” use verified login methods instead. - Only use when explicitly required for read-only demos - Automatically use `auth-tool-cloudbase` to turn on `Anonymous Login` through `manageAppAuth` (must be explicitly enabled first) ```js // Anonymous login is disabled by default β€” must be explicitly enabled via auth-tool const { data, error } = await auth.signInAnonymously() ``` **6. OAuth (Google/WeChat)** - Automatically use `auth-tool-cloudbase` to turn on `Google Login` or `WeChat Login` through `manageAppAuth` ```js const { data, error } = await auth.signInWithOAuth({ provider: 'google' }) window.location.href = data.url // Auto-complete after callback ``` **7. Custom Ticket** ```js await auth.signInWithCustomTicket(async () => { const res = await fetch('/api/ticket') return (await res.json()).ticket }) ``` **8. ID Token (Third-party token validation)** ```js // Direct login with a third-party JWT/OAuth token (e.g. from native SDK) const { data, error } = await auth.signInWithIdToken({ provider: 'wechat', // or 'google', 'github', etc. token: '', }) ``` **9. Upgrade Anonymous** ```js const sessionResult = await auth.getSession() const upgradeResult = await auth.signUp({ phone: '13800000000', anonymous_token: sessionResult.data.session.access_token, }) await upgradeResult.data.verifyOtp({ token: '123456' }) ``` --- ## User Management ```js // Sign out const signOutResult = await auth.signOut() // Get user profile only after auth.getSession() has returned a real session const sessionResult = await auth.getSession() if (!sessionResult.data?.session) throw new Error('Not signed in') const userResult = await auth.getUser() console.log( userResult.data.user.email, userResult.data.user.phone, userResult.data.user.user_metadata?.nickName, ) // Update user (except email, phone) const updateProfileResult = await auth.updateUser({ nickname: 'New Name', gender: 'MALE', avatar_url: 'url', }) // Update user (email or phone) const updateEmailResult = await auth.updateUser({ email: 'new@example.com' }) const verifyEmailResult = await updateEmailResult.data.verifyOtp({ email: 'new@example.com', token: '123456', }) // Change password (logged in) const resetPasswordResult = await auth.resetPasswordForOld({ old_password: 'old', new_password: 'new', }) // Reset password (forgot) const reauthResult = await auth.reauthenticate() const forgotPasswordResult = await reauthResult.data.updateUser({ nonce: '123456', password: 'new', }) // Link third-party const linkIdentityResult = await auth.linkIdentity({ provider: 'google' }) // View/Unlink identities const identitiesResult = await auth.getUserIdentities() const unlinkIdentityResult = await auth.unlinkIdentity({ provider: identitiesResult.data.identities[0].id, }) // Delete account const deleteMeResult = await auth.deleteMe({ password: 'current' }) // Listen to state changes const authStateSubscription = auth.onAuthStateChange((event, session, info) => { // INITIAL_SESSION, SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED, USER_UPDATED, PASSWORD_RECOVERY, BIND_IDENTITY }) // Get access token const sessionResult = await auth.getSession() await fetch('/api/protected', { headers: { Authorization: `Bearer ${sessionResult.data.session?.access_token}` }, }) // Refresh session (extend token validity) const refreshResult = await auth.refreshSession() // uses current refresh_token // or with explicit token: await auth.refreshSession(refresh_token) // Set session manually (e.g. from external auth flow or SSR hydration) const setResult = await auth.setSession({ refresh_token: '' }) // Refresh user (sync latest user data from server) const refreshUserResult = await auth.refreshUser() ``` --- ## User Type ```ts declare type User = { id: any aud: string role: string[] email: any email_confirmed_at: string phone: any phone_confirmed_at: string confirmed_at: string last_sign_in_at: string app_metadata: { provider: any providers: any[] } user_metadata: { name: any picture: any username: any gender: any locale: any uid: any nickName: any avatarUrl: any location: any hasPassword: any } identities: any created_at: string updated_at: string is_anonymous: boolean } ``` --- ## Complete Example ```js class PhoneLoginPage { async sendCode() { const phone = document.getElementById('phone').value if (!/^1[3-9]\d{9}$/.test(phone)) return alert('Invalid phone') const { data, error } = await auth.signInWithOtp({ phone }) if (error) return alert('Send failed: ' + error.message) this.verifyOtp = data.verifyOtp document.getElementById('codeSection').style.display = 'block' this.startCountdown(60) } async verifyCode() { const code = document.getElementById('code').value if (!code) return alert('Enter code') if (!this.verifyOtp) return alert('Send the code first') const { data, error } = await this.verifyOtp({ token: code }) if (error) return alert('Verification failed: ' + error.message) console.log('Login successful:', data.user) window.location.href = '/dashboard' } startCountdown(seconds) { let countdown = seconds const btn = document.getElementById('resendBtn') btn.disabled = true const timer = setInterval(() => { countdown-- btn.innerText = `Resend in ${countdown}s` if (countdown <= 0) { clearInterval(timer) btn.disabled = false btn.innerText = 'Resend' } }, 1000) } } ```