Better Auth plugin for Capacitor/Ionic mobile apps. Provides offline-first authentication with persistent storage, OAuth flow support, and session management.
Better Auth plugin for Capacitor/Ionic mobile apps. Provides offline-first authentication with persistent storage, OAuth flow support, and session management.
@capacitor/preferences for offline accesspnpm add better-auth-capacitor @capacitor/preferences
pnpm add @capacitor/app @capacitor/browser
pnpm add @capacitor/network
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.
capacitor({
/**
* Disable origin override for Capacitor API routes
* When set to true, the origin header will not be overridden
*/
disableOriginOverride: false,
})
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:
capacitorClient plugin automaticallydisableDefaultFetchPlugins: 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)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',
}),
],
})
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
}
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}`,
},
})
}
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' })
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()
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()
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
<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>
When initiating OAuth sign-in, use relative callback URLs:
await authClient.signIn.social({
provider: 'google',
callbackURL: '/auth/callback', // Will become myapp://auth/callback
})
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.
import { isNativePlatform } from 'better-auth-capacitor/client'
if (isNativePlatform()) {
// Running in Capacitor native app
}
else {
// Running in web browser
}
better-auth-capacitor)| Export | Description |
|---|---|
capacitor(options?) |
Server-side Better Auth plugin for Capacitor |
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 |
better-auth-capacitor/plugins)| Export | Description |
|---|---|
lastLoginMethodClient(config?) |
Track last used login method |
better-auth >= 1.0.0@better-auth/core >= 1.0.0@capacitor/core >= 6.0.0@capacitor/preferences >= 6.0.0@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)MIT
We use cookies
We use cookies to analyze traffic and improve your experience. You can accept or reject analytics cookies.