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.

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

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 passez des cookies aux bearer tokens avec le bearer plugin. L'e-mail et le mot de passe fonctionnent via SDK tel quel. Pour Google et Apple, récupérez un ID token avec un plugin natif comme @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 rencontrées lors du passage d'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 une WebView native. Comparée à un site web classique, trois choses 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 Often same site, cookies just work Domaine différent, les cookies tiers sont bloqués par WKWebView
Redirections OAuth La redirection plein écran fonctionne La redirection sort de votre application 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 importe pour Apple. Les tokens de la feuille native iOS utilisent votre bundle ID comme audience, tandis que les tokens web et Android utilisent 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'en- set-auth-token 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 la 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. Les setData et getData méthodes sont réservées au natif. Sur le web, utilisez les cookies ou sessionStorage en utilisant Capacitor.isNativePlatform().

Étape 3 : E-mail et mot de passe

Aucun plugin requis. 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 de l'e-mail et la réinitialisation du mot de passe, Better Auth envoie des liens qui ouvrent une URL. Dirigez callbackURL ce lien vers une page de votre domaine également configurée comme iOS Universal Link et Android App Link, afin 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 avec les clés de Google et l'ID client configuré, crée ou lie l'utilisateur, puis renvoie une session. Le bearer token arrive dans le 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, puis 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. Pour l'inclure dans 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 modèle. Sur iOS avec Limited Login, vous obtenez un idToken, ailleurs vous passez le token d'accès. Le page d'intégration Better Auth contient l'exemple complet pour Facebook.

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

De Better Auth genericOAuth le plugin fournit des helpers pour Auth0, Okta, Keycloak et Microsoft Entra ID, ainsi qu'une configuration manuelle pour tout serveur OIDC. Pour 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. Voici 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 à usage unique de courte durée (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'auth et reçoit une session bearer classique.

Cela implique plus d'éléments mobiles que le transfert de token ID. Si vous avez seulement besoin du SSO entreprise sans la base de données utilisateur de Better Auth, se connecter directement via le 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 depuis le 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 de getSession accepter Authorization: Bearer <token> de la même manière qu'il accepte le cookie.

Troubleshooting

Problème Cause Solution
Invalid origin ou 403 lors de la connexion Capacitor origine non approuvée Ajouter 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 les domaines Utilisez le plugin bearer et envoyez le token
set-auth-token est toujours null Header non exposé Ajouter exposeHeaders: ['set-auth-token'] au CORS
Google : disallowed_useragent Page OAuth chargée dans la WebView Utilisez la connexion native et le idToken handoff
Le token ID Google a été rejeté L'audience ne correspond pas au client ID configuré Décoder le token avec SocialLogin.decodeIdToken et comparer aud avec clientId
Le token ID Apple a été rejeté sur iOS L'audience est le bundle ID Définir 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 après le redémarrage de l'app Token conservé uniquement en mémoire Persistez avec setData et chargez-l’avant la première requête

Déploiement de la partie mobile

Les changements d'auth sont rarement natifs. Les origines et fournisseurs de confiance sont configurés côté serveur, et le client code est en TypeScript, donc la plupart des correctifs peuvent être livrés via les live updates Capgo plutôt qu'une release sur les stores. Vous n'avez besoin d'un nouveau binaire 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 processus de revue classique.

Les derniers articles du blog

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