跳转至正文

如何在 Capacitor 应用中使用 Better Auth(2026 指南)

在 Capacitor 8 应用中使用 Better Auth:可信来源、使用 Bearer Token 替代 Cookie、邮箱密码登录、原生 Google 和 Apple 登录,以及问题修复。

由 Better Auth 支持的 Capacitor 应用注册界面

要在 Capacitor 应用中使用 Better Auth,请安装 better-auth 客户端到你的 Web code 中,将其指向你的 Better Auth 服务器,并添加 Capacitor 源(capacitor://localhost 在 iOS 上, https://localhost 在 Android 上) 来 trustedOrigins,并将 cookie 切换为 bearer token,配合 bearer plugin。邮箱和密码可直接通过 SDK 使用,无需改动。对于 Google 和 Apple,请用原生 plugin 获取 ID token,例如 @capgo/capacitor-social-login ,并传递给 authClient.signIn.social({ idToken }).

本指南介绍服务端与客户端配置、token 存储、社交登录、企业级提供商,以及将 Better Auth Web 应用迁移到 Capacitor 8 时常见的错误。

Better Auth 在 Capacitor 中运行时会有什么变化?

Capacitor 应用是在原生 WebView 中由本地 Origin 提供服务的 Web 应用。与普通网站相比,有三点不同:

主题 在网站上 在 Capacitor 应用中
来源 https://app.example.com capacitor://localhost (iOS), https://localhost (Android)
认证服务器 通常在同一站点,Cookie 直接生效 不同域名时,WKWebView 会阻止第三方 Cookie
OAuth 重定向 整页重定向没问题 重定向会离开你的打包应用,Google 会阻止 WebView

解决方案是:信任 Capacitor 来源,使用 bearer token,并使用原生插件进行社交登录。

第 1 步:配置 Better Auth 服务器

此示例使用 Hono,但 Better Auth 配置在 Express、Next.js 路由处理器或任何其他运行时中均相同。

bun add better-auth hono
// auth.ts
import { betterAuth } from 'better-auth';
import { bearer } from 'better-auth/plugins';
import { db } from './db'; // your Drizzle, Prisma, Kysely or pg instance

export const auth = betterAuth({
  baseURL: process.env.BETTER_AUTH_URL, // https://auth.example.com
  database: db,
  emailAndPassword: {
    enabled: true,
  },
  socialProviders: {
    google: {
      clientId: process.env.GOOGLE_WEB_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    },
    apple: {
      clientId: process.env.APPLE_SERVICE_ID as string,
      clientSecret: process.env.APPLE_CLIENT_SECRET as string,
      appBundleIdentifier: process.env.APPLE_BUNDLE_ID as string, // com.example.app
    },
  },
  trustedOrigins: [
    'capacitor://localhost', // iOS WebView
    'https://localhost', // Android WebView (androidScheme https, the default)
    'https://app.example.com', // your web build
    'https://appleid.apple.com',
  ],
  plugins: [bearer()],
});

appBundleIdentifier 这对 Apple 很重要。来自原生 iOS 界面的 token 以你的 bundle ID 作为受众,而 Web 和 Android 的 token 以 Services ID 作为受众。Better Auth 使用此字段来接受两者。

挂载 handler,并允许来自应用 origin 的 CORS。暴露 set-auth-token header,否则客户端永远看不到 bearer token:

// server.ts
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { auth } from './auth';

const app = new Hono();

app.use(
  '/api/auth/*',
  cors({
    origin: ['capacitor://localhost', 'https://localhost', 'https://app.example.com'],
    allowHeaders: ['Content-Type', 'Authorization'],
    allowMethods: ['GET', 'POST', 'OPTIONS'],
    exposeHeaders: ['set-auth-token'],
    credentials: true,
  }),
);

app.on(['GET', 'POST'], '/api/auth/*', (c) => auth.handler(c.req.raw));

export default app;

运行 bunx @better-auth/cli migrate (或 generate (用于你的 ORM)来创建表。

第 2 步:在应用中创建 auth client

bun add better-auth @capgo/capacitor-native-biometric
bunx cap sync

将 session token 存入 Keychain 或 Keystore,并在内存中保留一份副本,以便客户端同步读取:

// auth-client.ts
import { createAuthClient } from 'better-auth/client';
import { NativeBiometric } from '@capgo/capacitor-native-biometric';

const TOKEN_KEY = 'session.token';
let sessionToken = '';

export async function loadSessionToken() {
  try {
    const { value } = await NativeBiometric.getData({ key: TOKEN_KEY });
    sessionToken = value;
  } catch {
    sessionToken = '';
  }
}

async function saveSessionToken(token: string) {
  sessionToken = token;
  if (token) {
    await NativeBiometric.setData({ key: TOKEN_KEY, value: token });
  } else {
    await NativeBiometric.deleteData({ key: TOKEN_KEY }).catch(() => {});
  }
}

export const authClient = createAuthClient({
  baseURL: 'https://auth.example.com',
  fetchOptions: {
    auth: {
      type: 'Bearer',
      token: () => sessionToken,
    },
    onSuccess: async (ctx) => {
      const token = ctx.response.headers.get('set-auth-token');
      if (token) await saveSessionToken(token);
    },
  },
});

export async function clearSession() {
  await saveSessionToken('');
}

调用 await loadSessionToken() 在应用渲染前,接着 authClient.getSession() 以恢复用户状态:

import { authClient, loadSessionToken } from './auth-client';

await loadSessionToken();
const { data: session } = await authClient.getSession();
if (session) {
  console.log('Signed in as', session.user.email);
}

如果你使用 React、Vue、Svelte 或 Solid,请导入 createAuthClient 从对应的子路径(better-auth/react, better-auth/vue,…)以获取 useSession hook。 fetchOptions 保持不变。 setData 以及 getData 方法仅适用于原生端。在 Web 端,请回退到 cookies 或 sessionStorage 使用 Capacitor.isNativePlatform().

步骤 3:邮箱和密码

无需插件。这些是普通的 HTTP 请求。

import { authClient, clearSession } from './auth-client';

export async function signUp(email: string, password: string, name: string) {
  const { data, error } = await authClient.signUp.email({ email, password, name });
  if (error) throw new Error(error.message);
  return data;
}

export async function signIn(email: string, password: string) {
  const { data, error } = await authClient.signIn.email({ email, password });
  if (error) throw new Error(error.message);
  return data;
}

export async function signOut() {
  await authClient.signOut();
  await clearSession();
}

对于邮箱验证和密码重置,Better Auth 会发送打开 URL 的链接。将 callbackURL 指向您域名上的一个页面,该页面同时配置为 iOS Universal Link 和 Android App Link,以便链接在手机上打开应用,在其他设备上打开网站。

步骤 4:原生 Google 登录

按照 如何使用 Capacitor 通过 Google 登录,然后:

import { SocialLogin } from '@capgo/capacitor-social-login';
import { authClient } from './auth-client';

await SocialLogin.initialize({
  google: {
    webClientId: 'WEB_CLIENT_ID.apps.googleusercontent.com',
    iOSClientId: 'IOS_CLIENT_ID.apps.googleusercontent.com',
    mode: 'online', // Better Auth needs the idToken, offline mode only returns a code
  },
});

export async function signInWithGoogle() {
  const { result } = await SocialLogin.login({ provider: 'google', options: {} });

  if (result.responseType !== 'online' || !result.idToken) {
    throw new Error('Google did not return an ID token');
  }

  const { data, error } = await authClient.signIn.social({
    provider: 'google',
    idToken: {
      token: result.idToken,
      accessToken: result.accessToken?.token,
    },
  });
  if (error) throw new Error(error.message);
  return data;
}

Better Auth 会根据 Google 的密钥和已配置的 client ID 验证 token,创建或关联用户,并返回 session。Bearer token 会在 set-auth-token header 中传入,然后由你的 onSuccess hook 进行存储。

第 5 步:Apple 原生登录

按以下说明配置 Apple: 如何使用 Capacitor 通过 Apple 登录。生成一个 nonce,传给 Apple,并将同一个值发送给 Better Auth:

import { SocialLogin } from '@capgo/capacitor-social-login';
import { authClient } from './auth-client';

export async function signInWithApple() {
  const nonce = crypto.randomUUID();

  const { result } = await SocialLogin.login({
    provider: 'apple',
    options: { scopes: ['email', 'name'], nonce },
  });

  if (!result.idToken) throw new Error('Apple did not return an ID token');

  const { data, error } = await authClient.signIn.social({
    provider: 'apple',
    idToken: {
      token: result.idToken,
      nonce,
      accessToken: result.accessToken?.token,
    },
  });
  if (error) throw new Error(error.message);
  return data;
}

Apple 只在首次授权时提供用户姓名。如果想把它写入用户记录,请在首次登录后立即用以下内容更新个人资料: result.profile.givenName 以及 familyName.

Facebook 也遵循相同的模式。在 iOS 上使用 Limited Login 时,你会得到一个 idToken,在其他地方传递 access token。 Better Auth 集成页面 包含完整的 Facebook 示例。

步骤 6:Okta、Auth0、Entra ID 及其他 OIDC 提供商

Better Auth 的 genericOAuth 插件内置了 Auth0、Okta、Keycloak 和 Microsoft Entra ID 的辅助函数,并支持为任意 OIDC 服务器进行手动配置。在 Web 构建中, authClient.signIn.oauth2({ providerId }) 其工作方式与任何网站相同。

在 iOS 和 Android 上,该流程需要使用系统浏览器,因为提供商的登录页面不应在您的 WebView 中加载。可行的模式如下:

  1. 应用通过系统浏览器打开您的 Better Auth 登录 URL(例如使用 SocialLogin.openSecureWindow 或应用内浏览器插件)。
  2. Better Auth 与提供商完成 OAuth 交互,并在服务器上创建会话。
  3. 您的回调页面将该会话转换为短时效的一次性令牌(Better Auth 提供了 oneTimeToken 插件)并重定向到如下 deep link: com.example.app://auth?token=....
  4. App 通过 auth client 验证一次性 token,并获得普通的 bearer 会话。

相比 ID token 传递方式,这种方式涉及的环节更多。如果你只需要企业级 SSO 而不需要 Better Auth 的用户数据库,直接通过提供商登录会更简单,请参阅 Okta, Auth0 以及 Entra ID 指南。

第 7 步:用会话保护你的 API

你自己的 API 可以通过 Better Auth 的服务端 API,从 bearer token 中读取会话:

import { Hono } from 'hono';
import { auth } from './auth';

const api = new Hono();

api.get('/me/orders', async (c) => {
  const session = await auth.api.getSession({ headers: c.req.raw.headers });
  if (!session) return c.json({ error: 'unauthorized' }, 401);
  return c.json(await listOrders(session.user.id));
});

bearer 插件让 getSession 接受 Authorization: Bearer <token> 与接受 cookie 的方式相同。

故障排除

问题 原因 修复
Invalid origin 或登录时返回 403 Capacitor 源不受信任 添加 capacitor://localhost 以及 https://localhost 改为 trustedOrigins
WebView 控制台报 CORS 错误 服务器 CORS 未列出应用 Origin 将其添加到 CORS 中间件,允许 Authorization
登录成功,但是 getSession 返回 null 依赖跨域 Cookie 使用 bearer 插件并发送 token
set-auth-token 始终是 null Header 未暴露 添加 exposeHeaders: ['set-auth-token'] 到 CORS
Google: disallowed_useragent OAuth 页面在 WebView 中加载 使用原生登录和 idToken handoff
Google ID token 被拒绝 Audience 与配置的 client ID 不匹配 使用以下命令解码 token: SocialLogin.decodeIdToken 并比较 aud 配合 clientId
iOS 上 Apple ID token 被拒绝 Audience 是 bundle ID 设置 appBundleIdentifier 在 Apple provider 中
Apple nonce 错误 发送给 Apple 和 Better Auth 的 nonce 不同 一次生成,多次复用
应用重启后 Session 丢失 Token 仅保存在内存中 持久化方式: setData 并在首次请求前加载

部署移动端

Auth 的变更很少涉及原生层。受信任的 Origin 和 Provider 是服务端配置,而客户端 code 是 TypeScript,因此大多数修复都可以直接 Capgo 实时更新 发布,而无需通过应用商店发版。只有添加原生插件或更改 URL schemes 时,才需要新的二进制包。

为 Capacitor 应用提供 Live Updates

当 Web 层出现线上 Bug 时,通过 Capgo 发布修复,而不用等 App Store 审核好几天。用户会在后台收到更新,原生变更则仍走正常审核流程。

博客最新动态

Capgo 为你提供打造真正专业的移动应用所需的最有价值的洞察。