メインコンテンツへスキップ

CapacitorアプリでBetter Authを使う方法(2026年版ガイド)

Better Auth を Capacitor 8 アプリで使う:trusted origins、Cookie の代わりに bearer token、メールとパスワード、ネイティブの Google/Apple サインイン、修正点。

Better Auth によるバックエンドを持つ Capacitor アプリのサインアップ画面

Capacitor アプリで Better Auth を使用するには、 better-auth クライアントを Web code にインストールし、Better Auth サーバーを指定して、Capacitor のオリジン(capacitor://localhost iOSでは適用され、 https://localhost Android の場合)を追加します trustedOrigins、そして、 bearer プラグインを使用します。メールとパスワード認証は SDK をそのまま使用できます。Google や Apple の場合、 @capgo/capacitor-social-login などのネイティブプラグインで ID トークンを取得し、 authClient.signIn.social({ idToken }).

このガイドでは、サーバーとクライアントのセットアップ、トークンの保存、ソーシャルサインイン、エンタープライズプロバイダー、および Better Auth の Web アプリを Capacitor 8 に移行する際に遭遇するエラーについて説明します。

Better Auth が Capacitor 内で実行される場合の変更点

Capacitor アプリは、ネイティブ WebView 内のローカルオリジンから配信される Web アプリです。通常の Web サイトと比較して、3 つの点が異なります。

項目 Web サイトの場合 Capacitor アプリの場合
オリジン https://app.example.com capacitor://localhost (iOS)、 https://localhost (Android)
認証サーバー 多くの場合、同一サイトなのでクッキーはそのまま動きます ドメインが異なる場合、WKWebView によりサードパーティークッキーがブロックされます
OAuth リダイレクト フルページのリダイレクトは問題ありません リダイレクトでバンドル済みアプリの外に出ると、Google が WebView をブロックします

解決策は、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 のシートから発行されるトークンは audience としてバンドル ID を持ち、Web と Android のトークンは Services ID を持ちます。Better Auth はこのフィールドで両方を受け入れます。

ハンドラーをマウントし、アプリのオリジンからのCORSを許可します。公開するのは set-auth-token ヘッダーです。そうしないと、クライアントから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:アプリで認証クライアントを作成

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

セッション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では、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 を開くリンクを送信します。リンク先には callbackURL iOS Universal Link と Android App Link としても設定済みの、ドメイン上のページを指定してください。これにより、スマートフォンではアプリ、それ以外ではウェブサイトが開きます。

ステップ 4: ネイティブの Google サインイン

以下で説明するように 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 のキーと設定済みのクライアント ID と照合し、ユーザーを作成またはリンクしてセッションを返します。ベアラートークンは set-auth-token ヘッダーに到達し、あなたの onSuccess フックがそれを保存します。

ステップ 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を受け取り、それ以外の環境ではアクセストークンを渡します。 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 内で読み込ませるべきではないため、システムブラウザでのフローが必要です。動作するパターンは以下の通りです:

  1. アプリはシステムブラウザで Better Auth のサインイン URL を開きます(例えば SocialLogin.openSecureWindow またはインアプリブラウザプラグインを使用)。
  2. Better Auth がプロバイダーとの OAuth フローを実行し、サーバー上でセッションを作成します。
  3. コールバックページがそのセッションを短命のワンタイムトークンに変換します(Better Auth にはこれ用の oneTimeToken プラグインがあります)し、ディープリンク(例: com.example.app://auth?token=....
  4. アプリは auth クライアントでワンタイムトークンを検証し、通常のベアラセッションを受信します。

ID トークンの引き継ぎよりも複雑な構成です。エンタープライズ SSO だけで Better Auth のユーザーデータベースが不要な場合、プロバイダーに直接サインインする方がシンプルです。 Okta, Auth0 と Entra ID ガイドを参照してください。

ステップ 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 と https://localhost まで trustedOrigins
WebView コンソールで CORS エラーが発生 サーバーの CORS にアプリのオリジンがリストされていない CORS ミドルウェアに追加し、許可してください Authorization
サインインは成功しますが getSession を返します null ドメイン間でCookieに依存する bearerプラグインを使用し、tokenを送信する
set-auth-token 常に null ヘッダーが公開されていない 追加 exposeHeaders: ['set-auth-token'] CORS へ
Google: disallowed_useragent WebView に読み込まれた OAuth ページ ネイティブサインインと idToken 引き継ぎ
Google ID トークンが拒否されました Audience が設定されたクライアント ID と一致しません トークンをデコードするには SocialLogin.decodeIdToken を比較します aud を使用して clientId
iOS で Apple ID トークンが拒否されました Audience は bundle ID です 設定 appBundleIdentifier Apple プロバイダーで
Apple nonce エラー Apple と Better Auth に異なる nonce が送信される 一度生成し、同じ値を再利用する
アプリ再起動後にセッションが失われる メモリ内にのみ保持されるトークン 永続化するには setData を使用し、最初のリクエスト前に読み込みます

モバイル側のデプロイ

認証の変更はネイティブ側であることは稀です。信頼できるオリジンやプロバイダーはサーバー設定であり、クライアントの code は TypeScript で書かれているため、ほとんどの修正は Capgo Live Updates で配信でき、ストアへのリリースは不要です。ネイティブプラグインの追加や URL スキームの変更を行った場合のみ、新しいバイナリが必要です。

Capacitor アプリ向けの Live Updates

Web レイヤーのバグが本番で発生したら、App Store の審査を何日も待たずに Capgo で修正を配信しましょう。ネイティブの変更は通常の審査フローに残したまま、ユーザーはバックグラウンドでアップデートを受け取れます。

最新のブログ記事

Capgoは、真にプロフェッショナルなモバイルアプリを作るために必要な最高のインサイトを提供します。