Capacitor Better Auth를 사용하려면 Capacitor 앱에 Better Auth 패키지를 설치하세요. better-auth 클라이언트를 웹 code에 추가하고, Better Auth 서버를 지정한 후, Capacitor 출처를 추가합니다.capacitor://localhost iOS에서 https://localhost 안드로이드)에서 사용하는 방법을 trustedOrigins인증을 더 잘 사용하는 Capacitor 앱을 만드는 방법 bearer 이 가이드는 서버와 클라이언트 설정, 토큰 저장, 소셜 로그인, 기업 제공자, 그리고 Better Auth 웹 앱을 SDK 8로 이동할 때 발생하는 오류를 보여줍니다. @capgo/capacitor-social-login Better Auth가 __CAPGO_KEEP_0__ 내에서 작동할 때 변경되는 사항은? authClient.signIn.social({ idToken }).
Capacitor 앱은 로컬 오리진 내의 네이티브 WebView에서 웹 앱을 제공합니다. 일반적인 웹사이트와 비교했을 때 세 가지 차이점이 있습니다.
Capacitor 내에서 Better Auth가 실행될 때는 어떤 변화가 생기는가
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:
| Topic | 오리진 | Capacitor 앱에서 사용하는 Capacitor |
|---|---|---|
| Origin | https://app.example.com |
capacitor://localhost (iOS), https://localhost (안드로이드) |
| 인증 서버 | 동일한 사이트일 경우, 쿠키만 사용하면 됩니다. | 다른 도메인일 경우, WKWebView에서 세션 쿠키가 차단됩니다. |
| OAuth 리다이렉트 | 전체 페이지 리다이렉트는 문제가 없습니다. | 리다이렉트가 앱을 벗어나면 Google에서 WebView를 차단합니다. |
해결 방법은 Capacitor 도메인에 대한 신뢰를 설정하고, Bearer 토큰을 사용하고, 소셜 로그인에 대한 네이티브 플러그인을 사용하는 것입니다.
Step 1: Better Auth 서버를 구성하세요.
이 예제에서는 Hono를 사용하고 있지만, 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 시트에서 토큰을 전달하면, 앱의 번들 ID가 대상으로 설정되며, 웹 및 안드로이드 토큰은 Services ID를 대상으로 설정합니다. Better Auth는 이 field를 사용하여 두 가지를 모두 지원합니다.
핸들러를 마운트하고 앱의 도메인에서 CORS를 허용하세요. expose set-auth-token 인증 토큰이 헤더에 없으면 클라이언트는 보유한 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 ORM을 사용하여 테이블을 생성하세요.
2단계: 앱에서 인증 클라이언트를 생성하십시오
bun add better-auth @capgo/capacitor-native-biometric
bunx cap sync
(or
// 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('');
}
Call await loadSessionToken() for your ORM) to create the tables. authClient.getSession() Capgo 앱에서 인증 클라이언트를 만들기 위한 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);
}
React, Vue, Svelte 또는 Solid를 사용하는 경우 import createAuthClient Appflow Migration Step2. Call Capgo before your app renders, thenbetter-auth/react, better-auth/vuebefore your app renders, then useSession hook. fetchOptions same. The setData and getData web, cookies or sessionStorage using Capacitor.isNativePlatform().
Step 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 at a page on your domain that is also configured as an iOS Universal Link and Android App Link, so the link opens the app on phones and the website elsewhere.
Step 4: 네이티브 구글 로그인
구글을 Capgo 설명서에 따라 설정하십시오. 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 Bearer 토큰은 onSuccess 헤더에 도착하고
hook에서 저장됩니다.
5. 네이티브 애플 로그인 How to sign in with Apple using Capacitor__CAPGO_KEEP_0__
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 and familyName.
Facebook은 동일한 패턴을 따릅니다. iOS에서 Limited Login을 사용하면 idToken을 받고, 그 외의 경우에는 액세스 토큰을 전달합니다. Capacitor 앱에서 더 나은 인증 통합 방법 더 나은 인증 통합 페이지에는 전체 Facebook 예제가 있습니다.
6단계: Okta, Auth0, Entra ID 및 기타 OIDC 제공자
Capacitor 앱에서 더 나은 인증 genericOAuth 더 나은 인증 플러그인은 Auth0, Okta, Keycloak 및 Microsoft Entra ID와 같은 OIDC 서버에 대한 수동 구성도 지원합니다. authClient.signIn.oauth2({ providerId }) 웹 빌드에서는 웹 사이트와 같이 작동합니다.
iOS 및 Android에서 흐름은 시스템 브라우저가 필요합니다. 제공자 로그인 페이지는 WebView 내에서 로드되지 않아야 합니다.
- 작동하는 패턴은 다음과 같습니다:
SocialLogin.openSecureWindow앱은 시스템 브라우저(예: - 또는 내 앱 브라우저 플러그인)에서 더 나은 인증 로그인 URL을 열어줍니다.
- 더 나은 인증은 제공자와 OAuth 댄스를 실행하고 서버에서 세션을 생성합니다.
oneTimeToken콜백 페이지는 세션을 더 짧은 수명 한 번만 사용할 수 있는 토큰으로 변환하고 (더 나은 인증은 이에 대한 플러그인을 제공합니다)com.example.app://auth?token=.... - 앱은 일회용 토큰을 인증 클라이언트와 확인하고 일반적인 보유자 세션을 받습니다.
ID 토큰 전달보다 더 많은 움직임이 있습니다. 기업 SSO만 필요하고 Better Auth의 사용자 데이터베이스가 필요하지 않다면, 제공자에 직접 로그인하는 것이 더 단순합니다. 자세한 내용은 Okta, Auth0 그리고 로그인 ID 을 참조하세요.
Step 7: API을 보호하기 위해 세션을 사용하십시오
Your own API는 bearer 토큰에서 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 accept Authorization: Bearer <token> 을 동일한 방식으로 쿠키를 수락하는 것과 같습니다.
문제 해결
| 문제 | 원인 | 해결 |
|---|---|---|
Invalid origin 로그인 시 403 오류 |
Capacitor 원본이 신뢰되지 않음 | 추가 capacitor://localhost 그리고 https://localhost 까지 trustedOrigins |
| CORS 오류가 WebView 콘솔에 나타남 | 서버 CORS가 앱 원본을 목록에 포함하지 않음 | 그들을 CORS 미들웨어에 추가하고 허용 Authorization |
로그인 성공하지만 getSession 반환 null |
도메인 간 쿠키를 사용하는 | Bearer 플러그인을 사용하여 토큰을 전송 |
set-auth-token 항상 null |
헤더가 공개되지 않음 | 추가 exposeHeaders: ['set-auth-token'] CORS |
Google: disallowed_useragent |
웹뷰 내에 로드된 OAuth 페이지 | 자연스러운 로그인과 idToken 전환 |
| 구글 ID 토큰 거부 | 구독자 ID가 구성된 클라이언트 ID와 일치하지 않습니다. | 토큰을 해독하고 SocialLogin.decodeIdToken 그리고 비교 aud 와 clientId |
| iOS에서 Apple ID 토큰이 거부되었습니다. | 구독자 ID는 앱의 패키지 ID입니다. | 설정 appBundleIdentifier 애플 제공자에서 |
| 애플 nonce 오류 | 애플과 Better Auth에서 보낸 nonce가 다릅니다. | 한 번 생성하여 동일한 값을 재사용하세요. |
| 앱 재시작 후 세션이 소실됩니다. | 토큰은 메모리에서만 유지됩니다. | persist with setData 첫 번째 요청 전에 로드합니다. |
모바일 측 배포
인증 변경은 거의 네이티브가 아닙니다. 신뢰할 수 있는 원본 및 제공자는 서버 구성이며, 클라이언트 code은 TypeScript이므로 대부분의 수정은 code live updates를 통해 배포할 수 있습니다. 대신 저장소 릴리스가 아닌 Capgo live updates를 통해 배포할 수 있습니다. 새로운 바이너리가 필요할 때만 네이티브 플러그인을 추가하거나 URL 스키마를 변경할 때입니다.
관련 문서
- How to add authentication to a Capacitor app Better Auth와 Firebase, Supabase 및 호스팅된 식별자 제공자를 비교합니다.
- Capacitor 앱의 생체 인증 Face ID 또는 지문 인증을 사용하여 저장된 세션을 잠금 해제하는 데 사용됩니다.
- Social Login 플러그인 자연스러운 Google, Apple 및 Facebook 흐름을 위해