跳过主要内容

如何在Capacitor应用中使用更好的认证(2026年指南)

如何在 Capacitor 应用中使用更好的身份验证:信任的源、代币而不是 cookie、电子邮件和密码、原生 Google 和 Apple 登录以及修复。

文章来源

马丁·多纳迪厄

作者

瓦莱里亚

审阅者

乔丹

编辑

如何在 Capacitor 应用中使用更好的身份验证(2026 年指南)

To use Better Auth in a Capacitor app, install the better-auth 在您的 web code 中添加客户端,指向您的 Better Auth 服务器,添加 Capacitor 源(capacitor://localhost 在 iOS 上, https://localhost On Android) 到 trustedOrigins, 并切换到使用 bearer 令牌代替 cookie 的 bearer 插件。电子邮件和密码通过 SDK 原样工作。对于 Google 和 Apple,使用本机插件获取 ID 令牌,然后将其传递给 @capgo/capacitor-social-login 本指南展示了服务器和客户端设置、令牌存储、社交登录、企业提供商和人们在将 Better Auth 网站应用移到 __CAPGO_KEEP_0__ 8 时遇到的错误。 authClient.signIn.social({ idToken }).

当 Better Auth 运行在 Capacitor 内部时会发生什么变化?

一个 Capacitor 应用是从本地源内嵌在原生 WebView 中的 web 应用。与普通网站相比,三点不同:

A Capacitor app is a web app served from a local origin inside a native WebView. Compared to a normal website, three things are different:

在网站上 在 __CAPGO_KEEP_0__ 应用中 In a Capacitor app
(iOS) https://app.example.com capacitor://localhost (iOS), https://localhost (Android)
认证服务器 通常在同一个站点,cookie就能正常工作 不同域名,第三方cookie被WKWebView阻止
OAuth重定向 全页面重定向是可以的 重定向会离开你的打包应用,Google会阻止WebViews

解决方案是:信任Capacitor的源地址,使用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来说,重要的是Tokens从原生iOSsheet中携带你的bundle ID作为受众,而web和Android tokens携带的则是Services ID。Better Auth使用这个字段来接受两者

将处理器挂载并允许从应用源地址的CORS。暴露 set-auth-token header, 否则客户端永远不会看到 Bearer 令牌

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

Run bunx @better-auth/cli migrate (or generate Compare Codepush Step2 Text

Compare Step Build Text

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

Compare Step Install Text

// 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() 为您的 ORM 创建表格 authClient.getSession() 步骤 2:在应用程序中创建认证客户端

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

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

在 Keychain 或 Keystore 中存储会话令牌,并在内存中保留一个副本,以便客户端可以同步读取它: createAuthClient 在应用程序渲染之前调用better-auth/react, better-auth/vue恢复用户: useSession hook。 fetchOptions 保持不变。 The setData 和 getData 方法仅在原生环境中可用。 在 Web 上,回退到 cookie 或 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 的链接。 指向您的域名上配置为 iOS Universal Link 和 Android App Link 的页面,这样链接在手机上可以打开应用,其他地方可以打开网站。 callbackURL 步骤 4:原生 Google 登录

配置 Google,如

如何使用 __CAPGO_KEEP_0__ 登录 Google How to sign in with Google using Capacitor然后:

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 的密钥和配置的客户端 ID 进行匹配,创建或链接用户,并返回会话。 该令牌将在 set-auth-token header 中到达, onSuccess hook 将其存储。

步骤 5:原生 Apple 登录

配置 Apple,如 如何使用 Capacitor 在 Apple 中登录。 生成一个随机数,传递给 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,在其他情况下,您将传递访问令牌。 Better Auth 有完整的Facebook示例。

步骤6:Okta,Auth0,Entra ID和其他OIDC提供商

Better Auth的 genericOAuth 插件带来了Auth0,Okta,Keycloak和Microsoft Entra ID的帮助程序,plus任何OIDC服务器的手动配置。 在Web构建中 authClient.signIn.oauth2({ providerId }) 与任何网站一样

在iOS和Android上,流程需要系统浏览器,因为提供商登录页面不能在您的WebView中加载。 成功的模式是:

  1. 应用程序在系统浏览器(例如 SocialLogin.openSecureWindow 或内置浏览器插件)中打开Better Auth的登录URL。
  2. Better Auth与提供商进行OAuth舞蹈,并在服务器上创建会话。
  3. 回调页面将该会话转换为短期一次性令牌(Better Auth有一个 oneTimeToken 插件来处理这个)并重定向到一个深度链接,如 com.example.app://auth?token=....
  4. 应用程序验证一次性令牌并从身份验证客户端接收正常的承载会话。

这比 ID 令牌传递多了几个部分。如果您只需要企业 SSO 且不需要更好的身份验证的用户数据库,则直接对提供商进行签名更简单,请参见 Okta, Auth0 和 身份验证 ID 指南。

第 7 步:保护您的API会话

您的自有API可以从承载令牌中读取会话,使用更好的身份验证的服务器API:

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));
});

承载插件使 getSession 接受 Authorization: Bearer <token> 以相同的方式接受 cookie。

故障排除

问题 原因 解决
Invalid origin 或 403 登录 Capacitor 源不受信任 添加 capacitor://localhost 和 https://localhost 到 trustedOrigins
WebView 控制台中的 CORS 错误 服务器 CORS 未列出应用程序源 将它们添加到 CORS 中间件中,允许 Authorization
登录成功但 getSession 返回 null 跨域域名使用cookie 使用Bearer插件并发送令牌
set-auth-token 始终 null 头信息未暴露 添加 exposeHeaders: ['set-auth-token'] 跨域资源共享
Google: disallowed_useragent OAuth页面在WebView中加载 使用原生登录和 idToken 手续转让
Google ID令牌被拒绝 受众不匹配配置的客户端ID 使用 SocialLogin.decodeIdToken 和比较 aud 与 clientId
Apple ID token iOS rejected 受众是应用程序ID 设置 appBundleIdentifier 在Apple提供商中
Apple nonce错误 Apple和Better Auth发送的nonce值不同 生成一次,重复使用相同的值
会话丢失后重启应用 仅在内存中保留令牌 使用 setData 并在第一个请求之前加载它

部署移动端

鉴权变化很少是原生。可信的源和提供者是服务器配置,客户端code是TypeScript,因此大多数修复可以通过code实时更新 Capgo 实时更新 相关指南

实时更新 Capacitor 应用

当 web 层 bug 活跃时,通过 Capgo 直接将修复推送给用户,而不是等待几天的应用商店审批。用户在后台接收更新,而原生变化仍然在正常审批路径中。

最新博客

Capgo 为您提供创建真正专业的移动应用所需的最佳见解。