要在 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 中加载。可行的模式如下:
- 应用通过系统浏览器打开您的 Better Auth 登录 URL(例如使用
SocialLogin.openSecureWindow或应用内浏览器插件)。 - Better Auth 与提供商完成 OAuth 交互,并在服务器上创建会话。
- 您的回调页面将该会话转换为短时效的一次性令牌(Better Auth 提供了
oneTimeToken插件)并重定向到如下 deep link:com.example.app://auth?token=.... - 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 应用中添加身份验证 将 Better Auth 与 Firebase、Supabase、Neon Auth (托管版 Better Auth) 及托管身份提供商进行了对比。
- Capacitor 应用中的生物识别认证 用于通过 Face ID 或指纹解锁已存储的会话。
- Social Login 插件 用于原生 Google、Apple 和 Facebook 登录流程。