packages/feed/docs/planning/mobile/04-privy-auth.md
✅ Privy OAuth: Officially documented by Privy for Capacitor. Tested Feb 13 2026 on Android emulator (Pixel 9 API 35) and real device (Pixel 10).
✅ Privy embedded wallet: SDK initializes, embedded wallet iframe loads, OAuth redirect fires correctly. The iframe uses postMessage for communication (not cookies), making it inherently WebView-compatible.
Reading the Privy SDK source (@privy-io/react-auth v3.13.1):
<iframe> loaded from Privy's API domain (e.g., auth.privy.io)postMessage (cross-origin messaging)postMessage — NOT via cookiessignMessage, signTypedData, sendTransaction) are performed by calling walletProxy.rpc() which sends the request to the iframe with the access tokenrecoverEmbeddedWallet) also passes the accessToken parameter explicitlyThis architecture is WebView-compatible because:
postMessage works cross-origin in WKWebView ✅postMessage, not via its own cookies/localStorage ✅Per Privy's official Capacitor docs, the OAuth integration requires:
@capacitor/browser and @capacitor/appAppUrlListener component that intercepts OAuth redirect deep links (checks for privy_oauth_code, privy_oauth_state, privy_oauth_provider params)customOAuthRedirectUrl in PrivyProvider config pointing to an HTTPS URL on your domain (e.g., https://feed.market/redirect)capacitor://localhost (iOS) and https://localhost (Android) as allowed originsapple-app-site-association (iOS) and assetlinks.json (Android) served from /.well-known/Info.plist URL schemes + Android AndroidManifest.xml intent filtersPrivy uses ASWebAuthenticationSession on iOS and Chrome Custom Tabs on Android — native platform flows, not WebView popups.
AppUrlListener (apps/mobile/src/components/AppUrlListener.tsx):
appUrlOpen events from Capacitorprivy_oauth_code, privy_oauth_state, privy_oauth_provider)PrivyProvider in the component treecustomOAuthRedirectUrl (apps/mobile/src/app/layout.tsx):
Providers component's privyConfigOverride prophttps://feed.market/redirectNEXT_PUBLIC_OAUTH_REDIRECT_URL env varCapacitor's WebView sends requests from capacitor://localhost (iOS) or https://localhost (Android). These origins are added to the middleware's CORS allowlist in apps/web/src/middleware.ts.
Pending: Set CORS_ALLOWED_ORIGINS=capacitor://localhost,https://localhost on Vercel production environment.
apiFetch() sends credentials: 'include' to include the privy-token httpOnly cookie. In a cross-origin context (Capacitor WebView → Vercel API), SameSite cookies won't be sent.
Fallback: apiFetch() also sends Authorization: Bearer <token> via getPrivyAccessToken(). The API middleware (packages/api/src/auth-middleware.ts) checks both cookie and header — cookie first, then Authorization header.
Status: Likely OK because of the Bearer token fallback, but needs explicit production testing.
From packages/shared/src/auth/privy-config.ts:
Primary: farcaster, email
Overflow: metamask, twitter, discord, telegram, phantom, rabby_wallet,
coinbase_wallet, rainbow, backpack
All of these use either:
@capacitor/browser plugin (Farcaster, Twitter, Discord, Telegram)