auth-web-cloudbase

Web frontend authentication with multiple login methods and complete user management for CloudBase. Supports 8 login methods: phone/email OTP, password, anonymous, OAuth (Google/WeChat), custom ticket, and anonymous account upgrade Includes full user management: sign out, profile updates, password changes, identity linking/unlinking, and account deletion Built-in auth state listeners and session management with access token retrieval for API calls Compatible with @cloudbase/js-sdk@2.24.0+ and requires CloudBase environment ID, region, and publishable key from auth-tool-cloudbase

INSTALLATION
npx skills add https://github.com/tencentcloudbase/skills --skill auth-web-cloudbase
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2c

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.

-

**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.

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.signUp({ username, password }) and auth.signInWithPassword({ username, password }) are the canonical username/password Web auth path
  • 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

// 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).
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
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:

// 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):

// 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 if needed

const { data: userData } = await auth.getUser()

const hasVerifiedIdentity = userData?.user &#x26;&#x26; (

  userData.user.phone_confirmed_at ||

  userData.user.email_confirmed_at ||

  userData.user.user_metadata?.username

)

// ❌ Do NOT use auth.getLoginState() — it's deprecated and returns

//    misleading data (uid/loginState) even without real login

// ❌ Do NOT use !!loginState or !!loginState.uid as auth checks

4. Registration

  • For username-style account systems, use username/password registration directly
  • Username must be 5-24 characters (letters, digits, underscores)
  • Do not switch to email OTP or phone OTP unless the task explicitly says the account identifier is an email address or phone number
  • When the task uses plain usernames such as admin, editor, or user01, the canonical form code is auth.signUp({ username, password })
// Username + Password

const usernameSignUp = await auth.signUp({

  username: 'newuser',

  password: 'pass123',

  nickname: 'User',

})

// 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:

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.

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)
// 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
const { data, error } = await auth.signInWithOAuth({ provider: 'google' })

window.location.href = data.url // Auto-complete after callback

7. Custom Ticket

await auth.signInWithCustomTicket(async () => {

  const res = await fetch('/api/ticket')

  return (await res.json()).ticket

})

8. ID Token (Third-party token validation)

// 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: '<jwt-or-oauth-token>',

})

9. Upgrade Anonymous

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

// Sign out

const signOutResult = await auth.signOut()

// Get user

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: '<token-from-server>' })

// Refresh user (sync latest user data from server)

const refreshUserResult = await auth.refreshUser()

User Type

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

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)

  }

}
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card