Aller au contenu principal

Comment utiliser Better Auth dans les apps Capacitor (guide 2026)

Utilisez Better Auth dans une app Capacitor 8 : origines de confiance, bearer tokens au lieu des cookies, e-mail et mot de passe, connexion native Google et Apple, et correctifs.

Écran d'inscription d'une application Capacitor avec Better Auth

Pour utiliser Better Auth dans une app Capacitor, installez le better-auth client dans votre code web, pointez-le vers votre serveur Better Auth, ajoutez les origines Capacitor (capacitor://localhost sur iOS, https://localhost sur Android) pour trustedOrigins, et passer des cookies aux bearer tokens avec le bearer plugin. L’authentification par e-mail et mot de passe fonctionne telle quelle via le SDK. Pour Google et Apple, obtenez un ID token avec un plugin natif tel que @capgo/capacitor-social-login et transmettez-l’à authClient.signIn.social({ idToken }).

Ce guide présente la configuration serveur et client, le stockage des tokens, la connexion sociale, les fournisseurs enterprise et les erreurs que l’on rencontre en portant une application web Better Auth vers Capacitor 8.

Ce qui change quand Better Auth tourne dans Capacitor

Une application Capacitor est une application web servie depuis une origine locale dans un WebView natif. Par rapport à un site web classique, trois aspects diffèrent :

Sujet Sur un site web Dans une application Capacitor
Origine https://app.example.com capacitor://localhost (iOS), https://localhost (Android)
Serveur d’auth Souvent le même site : les cookies fonctionnent Domaine différent, les cookies tiers sont bloqués par WKWebView
redirections OAuth Une redirection pleine page suffit La redirection quitte votre app bundle, Google bloque les WebViews

Les solutions sont : faire confiance aux origines Capacitor, utiliser des bearer tokens et utiliser des plugins natifs pour le social login.

Étape 1 : configurer le serveur Better Auth

Cet exemple utilise Hono, mais la config Better Auth est la même pour Express, les route handlers Next.js ou tout autre runtime.

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 est important pour Apple. Les tokens issus de la feuille iOS native portent votre bundle ID comme audience, tandis que les tokens web et Android portent le Services ID. Better Auth utilise ce champ pour accepter les deux.

Montez le handler et autorisez le CORS depuis les origines de l'app. Exposez l' set-auth-token en-tête, sinon le client ne verra jamais le 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;

Exécutez bunx @better-auth/cli migrate (ou generate pour votre ORM) pour créer les tables.

Étape 2 : Créer le client d'auth dans l'app

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

Stockez le token de session dans le Keychain ou le Keystore, et gardez une copie en mémoire pour que le client puisse le lire de manière synchrone :

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

Appelez await loadSessionToken() avant le rendu de votre app, puis authClient.getSession() pour restaurer l'utilisateur :

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

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

Si vous utilisez React, Vue, Svelte ou Solid, importez createAuthClient depuis le sous-chemin correspondant (better-auth/react, better-auth/vue, …) pour obtenir le useSession hook. Le fetchOptions restent identiques. Le setData et getData Les méthodes sont uniquement natives. Sur le web, utilisez des cookies ou sessionStorage en utilisant Capacitor.isNativePlatform().

Étape 3 : E-mail et mot de passe

Aucun plugin nécessaire. Ce sont de simples appels 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();
}

Pour la vérification d’e-mail et la réinitialisation du mot de passe, Better Auth envoie des liens qui ouvrent une URL. Point callbackURL vers une page de votre domaine également configurée comme Universal Link iOS et App Link Android, pour que le lien ouvre l’application sur mobile et le site web ailleurs.

Étape 4 : Connexion Google native

Configurez Google comme décrit dans Comment se connecter avec Google via Capacitor, puis :

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 vérifie le token par rapport aux clés de Google et à l'ID client configuré, crée ou lie l'utilisateur, puis renvoie une session. Le bearer token arrive dans l' set-auth-token header et votre onSuccess hook le stocke.

Étape 5 : Connexion native Apple

Configurez Apple comme décrit dans Comment se connecter avec Apple via Capacitor. Générez un nonce, transmettez-l’à Apple et envoyez la même valeur à 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 ne partage le nom de l'utilisateur que lors de la première autorisation. Si vous le voulez sur la fiche utilisateur, mettez à jour le profil juste après la première connexion avec result.profile.givenName et familyName.

Facebook suit le même schéma. Sur iOS avec Limited Login, vous obtenez un idToken, ailleurs vous passez le token d'accès. La page d'intégration Better Auth contient l'exemple Facebook complet.

Étape 6 : Okta, Auth0, Entra ID et autres fournisseurs OIDC

de Better Auth genericOAuth le plugin propose des helpers pour Auth0, Okta, Keycloak et Microsoft Entra ID, ainsi qu'une config manuelle pour tout serveur OIDC. Sur le build web, authClient.signIn.oauth2({ providerId }) fonctionne comme sur n'importe quel site.

Sur iOS et Android, le flux nécessite le navigateur système, car la page de connexion du fournisseur ne doit pas se charger dans votre WebView. Le pattern qui fonctionne :

  1. L'application ouvre votre URL de connexion Better Auth dans le navigateur système (par exemple avec SocialLogin.openSecureWindow ou un plugin de navigateur in-app).
  2. Better Auth gère le flux OAuth avec le fournisseur et crée la session sur le serveur.
  3. Votre page de callback convertit cette session en un token éphémère à usage unique (Better Auth possède un oneTimeToken plugin pour cela) et redirige vers un deep link tel que com.example.app://auth?token=....
  4. L'application vérifie le token à usage unique avec le client d'authentification et reçoit une session bearer classique.

Cela fait plus d'éléments à gérer que le transfert de token ID. Si vous avez seulement besoin du SSO entreprise sans la base de données utilisateur de Better Auth, s'authentifier directement auprès du fournisseur est plus simple, consultez les Okta, Auth0 et Entra ID guides.

Étape 7 : Protégez votre API avec la session

Votre propre API peut lire la session à partir du bearer token avec l’API serveur de 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));
});

Le plugin bearer permet à getSession d'accepter Authorization: Bearer <token> de la même manière qu'il accepte le cookie.

Troubleshooting

Problème Cause Corriger
Invalid origin ou 403 lors de la connexion Capacitor origine non approuvée Ajoutez capacitor://localhost et https://localhost vers trustedOrigins
Erreur CORS dans la console WebView Le CORS du serveur ne liste pas les origines de l'app Ajoutez-les à votre middleware CORS, autorisez Authorization
La connexion réussit mais getSession renvoie null S'appuyer sur les cookies entre domaines Utilisez le plugin bearer et envoyez le token
set-auth-token est toujours null Header non exposé Ajoutez exposeHeaders: ['set-auth-token'] au CORS
Google : disallowed_useragent Page OAuth chargée dans la WebView Utilisez la connexion native et le idToken Transfert
Token ID Google rejeté L'audience ne correspond pas au client ID configuré Décoder le token avec SocialLogin.decodeIdToken et comparer aud par clientId
Token ID Apple rejeté sur iOS L'audience est le bundle ID Définissez appBundleIdentifier dans le provider Apple
Erreur de nonce Apple Nonce différent envoyé à Apple et Better Auth Générez une fois, réutilisez la même valeur
Session perdue au redémarrage de l'app Token conservé uniquement en mémoire Persister avec setData et le charger avant la première requête

Déploiement de la partie mobile

Les changements d'auth sont rarement natifs. Les origines et les providers de confiance se configurent côté serveur, et le client code est en TypeScript, donc la plupart des correctifs peuvent être déployés via les live updates Capgo au lieu d'une nouvelle version sur les stores. Un nouveau binaire n'est nécessaire que si vous ajoutez un plugin natif ou modifiez les URL schemes.

Live updates pour les apps Capacitor

Lorsqu'un bug de la couche web est en ligne, déployez le correctif via Capgo au lieu d'attendre des jours l'approbation des stores. Les utilisateurs reçoivent la mise à jour en arrière-plan, tandis que les changements natifs suivent le circuit de validation habituel.

Les derniers articles du blog

Capgo vous offre les meilleures informations pour créer une application mobile vraiment professionnelle.