要在Capacitor应用中使用更好的身份验证,安装 better-auth 在您的code中安装客户端,指向您的更好身份验证服务器,添加Capacitor起源(capacitor://localhost 在iOS中 https://localhost 在Android中)到 trustedOrigins使用更好的 Capacitor 应用程序身份验证 bearer 在Capacitor应用中使用更好的身份验证
plugin. 邮箱和密码通过SDK原样工作。对于Google和Apple,使用native插件获取ID令牌 @capgo/capacitor-social-login 客户端 authClient.signIn.social({ idToken }).
本指南展示了服务器和客户端设置、令牌存储、社交登录、企业提供商以及人们在将Better Auth web应用移到Capacitor 8时遇到的错误。
当Better Auth运行在Capacitor中的时候,会发生什么变化?
一个Capacitor应用是一个在本地源中服务的Web应用,内置在一个原生WebView中。与一个正常的网站相比,三件事情是不同的:
| 主题 | 在一个网站上 | 在一个Capacitor应用中 |
|---|---|---|
| 源 | https://app.example.com |
capacitor://localhost (iOS) https://localhost (Android) |
| 认证服务器 | 通常同站,Cookie就能正常工作 | 域名不同,第三方Cookie被WKWebView阻止 |
| OAuth 重定向 | 全屏重定向是可以的 | 重定向会离开你的打包应用,Google会阻止WebViews |
解决方案是:信任Capacitor源地址,使用令牌认证,使用原生插件进行社交登录
步骤 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 框架中传递,带有你的 bundle ID 作为受众,而 Web 和 Android 令牌则带有 Services ID。Better Auth 使用此字段来接受两者
挂载处理器并允许从应用源地址的 CORS。暴露 set-auth-token header,否则客户端永远不会看到令牌:
// 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 (or generate 挂载处理器并允许从应用源地址的 CORS。暴露 header,否则客户端永远不会看到令牌:
第 2 步:在应用程序中创建认证客户端
bun add better-auth @capgo/capacitor-native-biometric
bunx cap sync
将会话令牌存储在 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 上,fallback 到 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, then:
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;
}
钩子存储了它。 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 的
Better Auth 的 genericOAuth Capacitor 应用程序的插件提供了 Auth0、Okta、Keycloak 和 Microsoft Entra ID 的帮助程序,以及任何 OIDC 服务器的手动配置。 在 web 构建中, authClient.signIn.oauth2({ providerId }) 在任何网站上都能正常工作。
在 iOS 和 Android 上,流程需要系统浏览器,因为提供者登录页面不能在 WebView 内加载。 成功的模式是:
- 应用程序在系统浏览器(例如使用或内置浏览器插件)中打开 Better Auth 登录 URL。
SocialLogin.openSecureWindowBetter Auth 与提供者进行 OAuth 舞蹈,并在服务器上创建会话。 - 回调页面将会话转换为短期一次性令牌(Better Auth 有一个插件来实现这一点),并重定向到一个深度链接,如
- 应用程序验证一次性令牌并从身份验证客户端接收正常的持有者会话。
oneTimeToken这比 ID 令牌传递多了几个步骤。如果您只需要企业 SSO 且不需要 Better Auth 的用户数据库,请直接对提供者进行签名,查看com.example.app://auth?token=.... - Okta
第 7 步:保护您的 API 使用会话
您的 API 可以使用 Better Auth 的服务器 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 and https://localhost 和 trustedOrigins |
| 到 | CORS错误在WebView控制台 | 服务器CORS不列出应用程序源 Authorization |
将它们添加到CORS中间件中,允许 getSession 登录成功但 null |
返回 | 跨域请求中依赖于Cookie的安全性问题使用Bearer令牌发送令牌 |
set-auth-token 总是 null |
Header 未暴露 | 添加 exposeHeaders: ['set-auth-token'] 总是 |
跨域请求 disallowed_useragent |
Google: | WebView 中加载 OAuth 页面 idToken 使用原生登录并进行 |
| 手动转移 | Google ID token 被拒绝 | 受众不匹配配置的客户端 ID SocialLogin.decodeIdToken 解码令牌并比较 aud 与 clientId |
| 苹果ID令牌在iOS中被拒绝 | 受众是应用程序ID | 设置 appBundleIdentifier 在苹果提供商中 |
| 苹果nonce错误 | 不同nonce发送到苹果和Better Auth | 生成一次,重复使用相同值 |
| 会话在应用程序重启后丢失 | 令牌仅在内存中保留 | 持久化 setData 并在第一个请求之前加载它 |
部署移动端
认证变化很少是原生。信任的源和提供者是服务器配置,客户端code是TypeScript,所以大多数修复可以通过 Capgo实时更新 而不是通过商店发布。您只需要一个新的二进制文件,当您添加一个原生插件或更改URL方案时。
相关指南
- How to add authentication to a Capacitor app 如何在__CAPGO_KEEP_0__应用中添加身份验证
- Biometric authentication in Capacitor apps __CAPGO_KEEP_0__应用中的生物识别身份验证
- 用于使用Face ID或指纹解锁存储的会话。 社交登录插件