OAuth Integration (Google, Facebook, OIDC)
The app runs social/OAuth sign-in natively on both platforms. Google (and most identity providers) reject OAuth inside an embedded WebView with disallowed_useragent, so the authorization step must run in a system browser and return to the app via a redirect.
Both platforms use the same thin model:
Native performs the authorization step only — it opens the provider in a system browser (with PKCE where supported) and receives the authorization
code.Native does not exchange the code. It hands the (unconsumed)
code— plus the PKCEcode_verifier— back to the web app at/_oauth_callback. The web app completes the token exchange — server-side for any backend, or client-side only for public PKCE clients (Google); Facebook is confidential and must exchange with its app secret server-side.
The flow works for any kind of website — server-rendered / MPA or client-rendered — not just a SPA. This keeps the web app the single owner of the token exchange and gives one identical contract on iOS and Android.
Flow
The web app navigates to the provider's authorization URL as it would on the web (e.g.
https://accounts.google.com/o/oauth2/v2/auth?client_id=<web-client>&…).Native intercepts that navigation (main-frame nav, and
window.open/target=_blankpopups). It carries overscope,state, and a small allowlist of standard params the web app may have set (access_type,prompt,login_hint,include_granted_scopes,nonce,hd), and substitutes its ownclient_id/redirect_uri/response_type/ PKCE — the outbound URL is only a trigger.Native restarts the flow in a system browser using the native client id and a custom-scheme redirect, adding PKCE (
code_challenge) for Google.iOS:
ASWebAuthenticationSession(url:callbackURLScheme:).Android: AppAuth Custom Tab; the redirect is caught by AppAuth's
RedirectUriReceiverActivity.
On completion native builds
<initialUrl>/_oauth_callback?…and loads it in the WebView.The web app's
/_oauth_callbackhandler completes the exchange against the native client — server-side for any backend, or client-side for a public PKCE client (Google).
The /_oauth_callback contract
Native loads this URL in the WebView on completion (identical shape on both platforms):
On failure: …&error=<description> and no code.
Param | Notes |
|---|---|
|
|
|
|
| The native client id native used (not the web client id the web app started with) |
| The custom-scheme redirect native used; the backend must send the same value in the exchange |
| Authorization code — not exchanged natively, so still valid |
| Echo of the |
| PKCE verifier the exchange needs to complete (Google). Absent for non-PKCE providers |
| Present only on failure |
Website changes required
The web app needs no change to trigger OAuth — it keeps navigating to the provider URL as it does on the web. Any kind of site works (server-rendered / MPA or client-rendered, not just a SPA). The changes are on the callback + token exchange:
/_oauth_callbackroute — accept the params above (new:platform,client_id,redirect_uri,code_verifier) and complete the token exchange (below). A server-rendered / MPA site handles this route server-side; a client-rendered app via its router.Token exchange — server-side (any backend), or client-side only for public PKCE clients:
Use the native
client_idfrom the callback (not the web client).Send the PKCE
code_verifierand the exact custom-schemeredirect_urifrom the callback.Send no client secret for Google (iOS/Android are public clients) — so a backend-less site can exchange Google client-side. Facebook is confidential — exchange with the Facebook app secret server-side (no PKCE); a backend-less site cannot support Facebook.
Map
provider+platform→ the correct client config (or trust the passedclient_id/redirect_uri).
Verify
stateand treaterroras a failed sign-in.The callback arrives as a full page load to
<initialUrl>/_oauth_callback(native does a full navigation, not a client-side route push), and always on the configuredinitialUrlhost. A JS-driven app must persist any pre-login state in storage/cookies, not memory — it cannot assume the callback is an in-app route transition.
Provider / cloud configuration
Google
Create a dedicated iOS OAuth client and an Android OAuth client (package name + signing SHA-1) in Google Cloud.
iOS: put the real
CLIENT_ID/REVERSED_CLIENT_IDinios/App/App/GoogleService-Info.plist. The custom-scheme redirect is<REVERSED_CLIENT_ID>:/oauth2redirect.Android: set the redirect scheme to the Android client's reversed client id (see build flags below).
Facebook — register iOS + Android platforms in the Facebook app; set the app id in the
FACEBOOK_APP_ID(iOS) / provider console; the redirect isfb<appid>://authorize.Register every custom-scheme redirect URI in the respective provider console.
Build flags
Native OAuth is off by default on both platforms; enable it once the real client ids + redirect scheme are configured.
Android (android/app/build.gradle)
Field | Default | Effect |
|---|---|---|
|
| Activates the AppAuth interception in |
|
| Custom-scheme for the redirect. Must equal the |
|
| Unrelated to OAuth return (the Custom-Tab result returns via |
iOS (user-defined build settings in project.pbxproj, surfaced via Info.plist)
Build setting |
| Default | Effect |
|---|---|---|---|
|
|
| Activates |
|
| (empty) | Facebook app id; also forms the |
Google config on iOS comes from GoogleService-Info.plist (CLIENT_ID/REVERSED_CLIENT_ID).
Platform notes
iOS —
OAuthCoordinator.swiftdrivesASWebAuthenticationSession. The callback scheme is not registered inCFBundleURLTypes:ASWebAuthenticationSessioncatches its owncallbackURLScheme, and registering the same scheme would risk the redirect routing toapplication(_:open:)instead of the session callback. PKCE usesCryptoKit(SHA-256). No new pod.Android —
MainActivityuses AppAuth (net.openid:appauth), authorization step only. PKCE is added by AppAuth automatically; the verifier is read back fromAuthorizationResponse.request.codeVerifierand forwarded to the web app. No native token storage.