Skip to main content
Mobile apps cannot safely store OAuth client secrets. Embedding a secret in an iOS or Android binary is not safe, it can be extracted. The standard workaround (PKCE without a secret) works for some providers but not all. The OAuth proxy sits in between: the mobile app kicks off an OAuth flow through KavachOS, which holds the client secret and performs the code exchange on the device’s behalf. The app gets back tokens via its custom URL scheme, never touching the secret directly.
The proxy works with any provider configured in KavachOS. The mobile app only needs to know the provider name and its own redirect URI.

How it works

Setup

1

Configure the plugin

lib/kavach.ts
2

Register the server callback URI with your provider

When registering the OAuth application with your provider (Google, GitHub, etc.), add the KavachOS callback URL as an allowed redirect URI:
The mobile app’s custom scheme (com.example.myapp://...) is not registered with the provider, only KavachOS’s server URL is.
3

Implement the flow in your mobile app

Mobile app (React Native / Expo)

Endpoints

/auth/oauth-proxy/start query parameters

/auth/oauth-proxy/start response

Redirect the user to authUrl. proxyState is managed internally and round-trips through the provider.

Callback redirect to mobile app

After a successful exchange, the server issues a 302 redirect to the mobile app URI with tokens as query parameters:
If the user denies the request or the provider returns an error, the redirect includes ?error=access_denied instead.

PKCE support

The proxy generates a PKCE code verifier and challenge for every flow. The verifier is stored server-side alongside the proxy state and is used when exchanging the authorization code. The mobile app never needs to supply its own verifier, the server handles this entirely, preventing authorization code interception attacks even for providers that do not require PKCE.
Tokens are passed as URL query parameters so that custom-scheme handlers on iOS and Android can read them. Treat them as you would any OAuth token, store them in the device’s secure keychain, not in plain storage.

Security

Redirect URI validation, only URIs in allowedRedirectUris are accepted. Exact matches and scheme-prefix matches (entries ending with ://) are supported. Everything else returns 400. State TTL, proxy state entries expire after 10 minutes by default. An expired or unknown state returns 400 and cannot be replayed. One-time state, the state entry is deleted before the token exchange network call, preventing replay attacks even if the callback is called twice. No open redirects, the final redirect destination always comes from the stored state entry, never from user-supplied query parameters at callback time.

Configuration reference

Last modified on April 18, 2026