跳转至正文

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

在 Capacitor 8 应用中使用 Better Auth:包含受信任的 origins、使用 bearer tokens 代替 cookies、邮箱密码登录、原生 Google 和 Apple 登录以及相关修复。

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

要在 Capacitor 应用中使用 Better Auth,请安装 better-auth 在你的 Web code 中安装 client,将其指向你的 Better Auth 服务器,并添加 Capacitor origins (capacitor://localhost 在 iOS 上, https://localhost 在 Android 上) 以 trustedOrigins,并将 cookie 切换为 bearer token,使用 bearer 插件。邮箱和密码登录通过 SDK 原样即可使用。对于 Google 和 Apple,用原生插件获取 ID token,例如 @capgo/capacitor-social-login 并将其传递给 authClient.signIn.social({ idToken }).

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

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

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

主题 在网站上 在 Capacitor 应用中
Origin https://app.example.com capacitor://localhost (iOS), https://localhost (Android)
Auth 服务器 通常同源,Cookie 可直接使用 跨域时,WKWebView 会拦截第三方 Cookie
OAuth 重定向 全页重定向没问题 重定向会脱离你的 Bundle App,且 Google 会拦截 WebView

解决方案是:信任 Capacitor 源,使用 Bearer Token,并使用原生插件进行 Social Login。

第 1 步:配置 Better Auth 服务器

本示例使用 Hono,但 Better Auth 的配置在 Express、Next.js route handlers 或其他运行时中都是通用的。

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 原生 Sheet 的 Token 会将你的 Bundle ID 作为 audience,而 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,这样链接就能在手机上直接打开 App,在其他设备上则打开网站。

第 4 步:原生 Google 登录

按照以下方式配置 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 仅在首次授权时共享用户名。如果你想将其保存到用户记录中,请在首次登录后立即使用以下方式更新 profile: result.profile.givenName 和 familyName.

Facebook 的流程相同。在开启 Limited Login 的 iOS 上,你会得到一个 idToken,在其他地方你只需传递 access token。 Better Auth 集成页面 提供了完整的 Facebook 示例。

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

Better Auth 的 genericOAuth plugin 内置了针对 Auth0、Okta、Keycloak 和 Microsoft Entra ID 的辅助工具,同时也支持对任何 OIDC 服务器进行手动配置。在 Web 端构建中, authClient.signIn.oauth2({ providerId }) 其工作方式与任何网站一致。

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

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

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

第 7 步:使用 session 保护你的 API

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

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 origin 不受信任 添加 capacitor://localhost 和 https://localhost 至 trustedOrigins
WebView 控制台中出现 CORS 错误 服务器 CORS 未列出 App 的 origin 将它们添加到 CORS 中间件中,允许 Authorization
登录成功但 getSession 返回 null 依赖跨域 Cookie 使用 bearer 插件并发送 token
set-auth-token 始终是 null Header 未暴露 添加 exposeHeaders: ['set-auth-token'] 给 CORS
Google: disallowed_useragent WebView 中加载了 OAuth 页面 使用原生登录和 idToken 移交
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 的变更很少涉及 Native 层。受信任的 Origin 和 Provider 是服务端配置,而客户端 code 是 TypeScript,因此大多数修复都可以通过 Capgo Live Updates 发布 而不是通过应用商店发布。只有在添加 Native plugin 或更改 URL schemes 时才需要新的二进制文件。

为 Capacitor 应用提供实时更新

当 Web 层 Bug 已上线时,通过 Capgo 推送修复,不必等 App Store 审核数天。用户会在后台获取更新,而原生变更仍走常规审核流程。

博客最新文章

Capgo 为你提供打造真正专业移动应用所需的深度洞察。