//better-auth-capacitorbyproductdevbook

better-auth-capacitor

Better Auth plugin for Capacitor/Ionic mobile apps. Provides offline-first authentication with persistent storage, OAuth flow support, and session management.

15
0
15
3
TypeScript

better-auth-capacitor

Better Auth plugin for Capacitor/Ionic mobile apps. Provides offline-first authentication with persistent storage, OAuth flow support, and session management.

Features

  • undefinedOffline-first authentication - Sessions are cached in @capacitor/preferences for offline access
  • undefinedOAuth flow support - Social login via system browser with deep link callbacks
  • undefinedFocus/Online managers - Automatic session refresh when app regains focus or connectivity
  • undefinedBearer token extraction - Easy access to auth tokens for API requests
  • undefinedLast login method tracking - Remember which method the user used to sign in

Installation

pnpm add better-auth-capacitor @capacitor/preferences

Optional dependencies for OAuth

pnpm add @capacitor/app @capacitor/browser

Optional dependency for online manager

pnpm add @capacitor/network

Server Setup

Add the capacitor() plugin to your Better Auth server configuration:

import { betterAuth } from 'better-auth'
import { capacitor } from 'better-auth-capacitor'

export const auth = betterAuth({
  // ... your config
  plugins: [
    capacitor(),
  ],
})

This registers the /capacitor-authorization-proxy endpoint and handles origin override for Capacitor native requests.

Options

capacitor({
  /**
   * Disable origin override for Capacitor API routes
   * When set to true, the origin header will not be overridden
   */
  disableOriginOverride: false,
})

Client Setup

The recommended way is using withCapacitor() which handles everything automatically:

import { withCapacitor } from 'better-auth-capacitor/client'
import { createAuthClient } from 'better-auth/client'

const authClient = createAuthClient(withCapacitor({
  baseURL: 'https://api.example.com',
}, {
  scheme: 'myapp',
  storagePrefix: 'better-auth',
}))

withCapacitor() does two things:

  • Adds capacitorClient plugin automatically
  • Sets disableDefaultFetchPlugins: true on native platforms, which prevents better-auth’s built-in redirect plugin from opening Safari/Chrome during OAuth (the native auth sheet handles it instead)
Manual setup (without wrapper)
import { capacitorClient, isNativePlatform } from 'better-auth-capacitor/client'
import { createAuthClient } from 'better-auth/client'

const authClient = createAuthClient({
  baseURL: 'https://api.example.com',
  disableDefaultFetchPlugins: isNativePlatform(),
  plugins: [
    capacitorClient({
      scheme: 'myapp',
      storagePrefix: 'better-auth',
    }),
  ],
})

Configuration Options

interface CapacitorClientOptions {
  /**
   * Prefix for storage keys
   * @default 'better-auth'
   */
  storagePrefix?: string

  /**
   * Prefix(es) for server cookie names to filter
   * Prevents infinite refetching when third-party cookies are set
   * @default 'better-auth'
   */
  cookiePrefix?: string | string[]

  /**
   * App scheme for deep links (e.g., 'myapp')
   * Used for OAuth callback URLs
   */
  scheme?: string

  /**
   * Disable session caching
   * @default false
   */
  disableCache?: boolean
}

Getting the Bearer Token

For making authenticated API requests outside of Better Auth:

import { getCapacitorAuthToken } from 'better-auth-capacitor/client'

const token = await getCapacitorAuthToken({
  storagePrefix: 'better-auth',
  cookiePrefix: 'better-auth',
})

if (token) {
  fetch('https://api.example.com/data', {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  })
}

Storing Token from Custom Auth Endpoints

If you have custom authentication endpoints that don’t use the Better Auth client:

import { clearCapacitorAuthToken, setCapacitorAuthToken } from 'better-auth-capacitor/client'

// After custom login endpoint
const response = await fetch('/api/auth/custom-login', {
  method: 'POST',
  body: JSON.stringify({ email: 'user@example.com' }),
})
const data = await response.json()

// Store the token in Capacitor Preferences
await setCapacitorAuthToken({
  token: data.session.token,
  expiresAt: data.session.expiresAt, // Optional, defaults to 7 days
  storagePrefix: 'better-auth',
  cookiePrefix: 'better-auth',
})

// Now getSession() will work correctly
const session = await authClient.getSession()

// To clear the token (custom logout)
await clearCapacitorAuthToken({ storagePrefix: 'better-auth' })

Last Login Method Plugin

Track which method the user last used to sign in:

import { capacitorClient } from 'better-auth-capacitor/client'
import { lastLoginMethodClient } from 'better-auth-capacitor/plugins'
import { createAuthClient } from 'better-auth/client'

const authClient = createAuthClient({
  baseURL: 'https://api.example.com',
  plugins: [
    capacitorClient({ scheme: 'myapp' }),
    lastLoginMethodClient({ storagePrefix: 'better-auth' }),
  ],
})

// Get the last used login method
const lastMethod = await authClient.getLastUsedLoginMethod()
// Returns: 'google', 'github', 'email', 'passkey', etc.

// Check if a specific method was last used
const wasGoogle = await authClient.isLastUsedLoginMethod('google')

// Clear the stored method
await authClient.clearLastUsedLoginMethod()

Plugin Actions

The capacitorClient plugin adds these actions to your auth client:

// Get stored cookie string for manual fetch requests
const cookie = await authClient.getCookie()

// Get cached session data for offline use
const session = await authClient.getCachedSession()

// Clear all stored auth data
await authClient.clearStorage()

OAuth Setup

iOS (Info.plist)

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>myapp</string>
    </array>
  </dict>
</array>

Android (AndroidManifest.xml)

<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="myapp" />
</intent-filter>

2. Callback URL Configuration

When initiating OAuth sign-in, use relative callback URLs:

await authClient.signIn.social({
  provider: 'google',
  callbackURL: '/auth/callback', // Will become myapp://auth/callback
})

Post-OAuth Navigation

On native, signIn.social() may not resolve after the auth session completes. Don’t rely on awaiting it for navigation. Instead, poll for the token change:

import { getCapacitorAuthToken } from 'better-auth-capacitor/client'

// 1. Snapshot token before starting OAuth
const tokenBefore = await getCapacitorAuthToken({ storagePrefix: 'better-auth' })

// 2. Start sign-in (fire-and-forget on native)
authClient.signIn.social({ provider: 'google', callbackURL: '/dashboard' })

// 3. Poll for token change
const poll = setInterval(async () => {
  const token = await getCapacitorAuthToken({ storagePrefix: 'better-auth' })
  if (token && token !== tokenBefore) {
    clearInterval(poll)
    // Token changed - auth succeeded, navigate!
    router.push('/dashboard')
  }
}, 500)

The plugin also dispatches a better-auth:session-update DOM event after successful OAuth, which can be used as an alternative.

Platform Detection

import { isNativePlatform } from 'better-auth-capacitor/client'

if (isNativePlatform()) {
  // Running in Capacitor native app
}
else {
  // Running in web browser
}

API Reference

Server Export (better-auth-capacitor)

Export Description
capacitor(options?) Server-side Better Auth plugin for Capacitor

Client Exports (better-auth-capacitor/client)

Export Description
withCapacitor(options, capacitorOpts?) Wrapper that adds plugin + disables redirect on native
capacitorClient(options?) Client-side Better Auth plugin for Capacitor
getCapacitorAuthToken(options?) Get bearer token from storage
setCapacitorAuthToken(options) Store token for custom auth endpoints
clearCapacitorAuthToken(options?) Clear stored auth token
isNativePlatform() Check if running in Capacitor native app
setupCapacitorFocusManager() Set up app focus tracking
setupCapacitorOnlineManager() Set up network connectivity tracking
normalizeCookieName(name) Normalize cookie name for storage
getCookie(cookie) Convert stored cookies to header string
getSetCookie(header, prevCookie?) Merge new cookies with existing
hasBetterAuthCookies(header, prefix) Check if header contains auth cookies
parseSetCookieHeader Re-exported from better-auth/cookies

Plugin Exports (better-auth-capacitor/plugins)

Export Description
lastLoginMethodClient(config?) Track last used login method

Requirements

  • better-auth >= 1.0.0
  • @better-auth/core >= 1.0.0
  • @capacitor/core >= 6.0.0
  • @capacitor/preferences >= 6.0.0

Optional

  • @capacitor/app >= 6.0.0 (for OAuth deep links, focus manager)
  • @capacitor/browser >= 6.0.0 (for OAuth browser opening)
  • @capacitor/network >= 6.0.0 (for online manager)

License

MIT

[beta]v0.14.0