Langsung ke konten utama

Cara Pakai Better Auth di Aplikasi Capacitor (Panduan 2026)

Gunakan Better Auth di aplikasi Capacitor 8: trusted origins, bearer token sebagai pengganti cookie, email dan password, sign-in native Google dan Apple, serta perbaikan.

Layar sign-up aplikasi Capacitor yang didukung Better Auth

Untuk menggunakan Better Auth di aplikasi Capacitor, instal better-auth client di web code Anda, arahkan ke server Better Auth Anda, tambahkan Capacitor origins (capacitor://localhost di iOS, https://localhost di Android) untuk trustedOrigins, dan beralih dari cookie ke bearer token dengan bearer plugin. Email dan password tetap berfungsi lewat SDK apa adanya. Untuk Google dan Apple, dapatkan ID token dengan native plugin seperti @capgo/capacitor-social-login dan berikan ke authClient.signIn.social({ idToken }).

Panduan ini membahas setup server dan client, penyimpanan token, social sign-in, enterprise provider, serta error yang sering muncul saat memindahkan web app Better Auth ke Capacitor 8.

Apa yang berubah saat Better Auth berjalan di dalam Capacitor

Aplikasi Capacitor adalah web app yang dijalankan dari local origin di dalam native WebView. Dibandingkan website biasa, ada tiga hal yang berbeda:

Topik Di website Di aplikasi Capacitor
Asal https://app.example.com capacitor://localhost (iOS), https://localhost (Android)
Server Auth Sering kali masih satu site, cookies langsung jalan Domain berbeda, third-party cookies diblokir oleh WKWebView
Redirect OAuth Full-page redirect tidak masalah Redirect keluar dari bundled app, Google memblokir WebView

Solusinya: percayai origin Capacitor, gunakan bearer token, dan pakai plugin native untuk social login.

Langkah 1: Konfigurasi server Better Auth

Contoh ini menggunakan Hono, tetapi konfigurasi Better Auth tetap sama untuk Express, route handler Next.js, atau runtime lainnya.

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 penting bagi Apple. Token dari sheet iOS native membawa bundle ID sebagai audience, sedangkan token web dan Android membawa Services ID. Better Auth memakai field ini untuk menerima keduanya.

Mount handler dan izinkan CORS dari origin aplikasi. Ekspos set-auth-token header, jika tidak klien tidak akan pernah melihat 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;

Jalankan bunx @better-auth/cli migrate (atau generate untuk ORM Anda) untuk membuat tabel.

Langkah 2: Buat auth client di aplikasi

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

Simpan session token di Keychain atau Keystore, dan simpan salinannya di memori agar klien dapat membacanya secara sinkron:

// 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('');
}

Panggil await loadSessionToken() sebelum aplikasi Anda dirender, kemudian authClient.getSession() untuk memulihkan pengguna:

import { authClient, loadSessionToken } from './auth-client';

await loadSessionToken();
const { data: session } = await authClient.getSession();
if (session) {
  console.log('Signed in as', session.user.email);
}

Jika Anda menggunakan React, Vue, Svelte, atau Solid, import createAuthClient dari subpath yang sesuai (better-auth/react, better-auth/vue, …) untuk mendapatkan useSession hook. fetchOptions tetap sama. The setData dan getData metode ini hanya untuk native. Di web, gunakan cookie atau sessionStorage menggunakan Capacitor.isNativePlatform().

Langkah 3: Email dan password

Tidak perlu plugin. Ini hanya panggilan HTTP biasa.

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();
}

Untuk verifikasi email dan reset password, Better Auth mengirimkan link yang membuka URL. Arahkan callbackURL ke halaman di domain Anda yang juga dikonfigurasi sebagai iOS Universal Link dan Android App Link, agar link tersebut membuka aplikasi di ponsel dan website di perangkat lain.

Langkah 4: Login Google native

Konfigurasikan Google seperti dijelaskan di Cara login dengan Google menggunakan Capacitor, lalu:

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 memverifikasi token terhadap kunci Google dan client ID yang dikonfigurasi, membuat atau menautkan user, lalu mengembalikan session. Bearer token masuk melalui set-auth-token header dan milik Anda onSuccess hook Anda menyimpannya.

Langkah 5: Native Apple sign-in

Konfigurasikan Apple seperti dijelaskan di Cara sign in dengan Apple menggunakan Capacitor. Buat nonce, teruskan ke Apple, dan kirim nilai yang sama ke 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 hanya membagikan nama user pada otorisasi pertama. Jika ingin nama itu ada di record user, update profil tepat setelah sign-in pertama dengan result.profile.givenName dan familyName.

Facebook mengikuti pola yang sama. Di iOS dengan Limited Login, Anda mendapatkan idToken, di tempat lain Anda meneruskan access token. The Halaman integrasi Better Auth berisi contoh lengkap Facebook.

Langkah 6: Okta, Auth0, Entra ID, dan provider OIDC lainnya

Better Auth memiliki genericOAuth plugin yang menyertakan helper untuk Auth0, Okta, Keycloak, dan Microsoft Entra ID, plus konfigurasi manual untuk server OIDC apa pun. Pada build web, authClient.signIn.oauth2({ providerId }) berjalan seperti di situs mana pun.

Di iOS dan Android, alur ini butuh browser sistem, karena halaman login provider tidak boleh dimuat di dalam WebView Anda. Pola yang berhasil:

  1. Aplikasi membuka URL sign-in Better Auth Anda di browser sistem (misalnya dengan SocialLogin.openSecureWindow atau plugin in-app browser).
  2. Better Auth menjalankan proses OAuth dengan provider dan membuat session di server.
  3. Halaman callback Anda mengubah session itu menjadi one-time token berumur pendek (Better Auth punya oneTimeToken plugin untuk ini) dan mengalihkan ke deep link seperti com.example.app://auth?token=....
  4. Aplikasi memverifikasi token sekali pakai dengan auth client dan menerima sesi bearer biasa.

Ini melibatkan lebih banyak komponen daripada handoff ID token. Jika Anda hanya membutuhkan enterprise SSO dan tidak memerlukan database pengguna Better Auth, masuk langsung ke provider lebih sederhana, lihat Okta, Auth0 dan Entra ID panduan.

Langkah 7: Lindungi API Anda dengan sesi

API Anda sendiri dapat membaca sesi dari bearer token melalui API server Better Auth:

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));
});

plugin bearer membuat getSession menerima Authorization: Bearer <token> dengan cara yang sama seperti menerima cookie.

Pemecahan Masalah

Masalah Penyebab Perbaikan
Invalid origin atau 403 saat sign-in origin Capacitor tidak dipercaya Tambah capacitor://localhost dan https://localhost ke trustedOrigins
Error CORS di konsol WebView CORS server tidak mencantumkan origin aplikasi Tambahkan ke middleware CORS Anda, izinkan Authorization
Sign-in berhasil tetapi getSession mengembalikan null Mengandalkan cookie lintas domain Gunakan plugin bearer dan kirim token
set-auth-token selalu null Header tidak terekspos Tambah exposeHeaders: ['set-auth-token'] ke CORS
Google: disallowed_useragent Halaman OAuth dimuat di WebView Gunakan native sign-in dan idToken handoff
ID token Google ditolak Audience tidak cocok dengan client ID yang dikonfigurasi Decode token dengan SocialLogin.decodeIdToken dan bandingkan aud dengan clientId
ID token Apple ditolak di iOS Audience adalah bundle ID Atur appBundleIdentifier di provider Apple
Error nonce Apple Nonce berbeda dikirim ke Apple dan Better Auth Generate sekali, gunakan nilai yang sama
Sesi hilang setelah aplikasi di-restart Token hanya disimpan di memori Simpan dengan setData dan muat sebelum request pertama

Deploy sisi mobile

Perubahan auth jarang bersifat native. Trusted origins dan provider diatur di server, dan client code adalah TypeScript, jadi sebagian besar perbaikan bisa dikirim via Live updates Capgo daripada merilis ulang di store. Anda hanya butuh binary baru saat menambahkan native plugin atau mengubah URL scheme.

Live updates untuk aplikasi Capacitor

Saat bug web-layer sudah live, kirim perbaikannya lewat Capgo alih-alih menunggu berhari-hari untuk persetujuan app store. Pengguna mendapat update di background, sementara perubahan native tetap mengikuti jalur review normal.

Terbaru dari Blog kami

Capgo memberikan insight terbaik yang Anda butuhkan untuk membuat aplikasi mobile yang benar-benar profesional.