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中加载。 成功的模式是:
- 应用程序在系统浏览器(例如
SocialLogin.openSecureWindow或内置浏览器插件)中打开Better Auth的登录URL。 - Better Auth与提供商进行OAuth舞蹈,并在服务器上创建会话。
- 回调页面将该会话转换为短期一次性令牌(Better Auth有一个
oneTimeToken插件来处理这个)并重定向到一个深度链接,如com.example.app://auth?token=.... - 应用程序验证一次性令牌并从身份验证客户端接收正常的承载会话。
这比 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 实时更新 相关指南
如何在__CAPGO_KEEP_0__应用中添加身份验证
- How to add authentication to a Capacitor app __CAPGO_KEEP_0__应用中的生物识别身份验证
- 生物识别认证在Capacitor应用中 使用面部识别或指纹解锁已存储的会话。
- 社交登录插件 原生Google、Apple和Facebook流程