diff --git a/android/README.md b/android/README.md index 0f7576ef..39a7a7ee 100644 --- a/android/README.md +++ b/android/README.md @@ -5,17 +5,6 @@ A hybrid mobile app built with **Next.js (TypeScript)** frontend, **Firebase bac Right now it's just a webview wrapper around the web application, but in the future it may contain native code as well. -/!\ This is still a work in progress, as google sign in does not work yet. Most of the issue is that Google doesn't allow to sign in inside the webview for security reasons. - -We could sign in in a browser with signInWithRedirect and then redirect to the webview, but I haven't found a way to make it come back to the app and store the token. - -We could also use a native sign in with SocialLogin, but it only runs on local assets, not on the webview that loads the remote sign-in page. So one way would be to force the webview to load the local assets, but it's not a clean solution and I haven't found a way to do it yet. - -Third solution is to implement a native app, not using webview, in React Native. - -If you can make it work, please contribute! - - This document describes how to: 1. Build and run the web frontend and backend locally 2. Sync and build the Android WebView wrapper diff --git a/docs/webview_oauth_signin.md b/docs/webview_oauth_signin.md index e4466a8f..4532f8a9 100644 --- a/docs/webview_oauth_signin.md +++ b/docs/webview_oauth_signin.md @@ -1,6 +1,6 @@ # WebView OAuth Sign-in -How to let a WebView-based app safely complete OAuth with PKCE, even though Google blocks sign-in *inside* WebViews. +How to let a WebView-based app safely complete OAuth, even though Google blocks sign-in *inside* WebViews. --- @@ -49,25 +49,18 @@ You register this scheme in your `AndroidManifest.xml` so Android knows which ap --- -## 3. How it fits into PKCE +## 3. -Let’s map the PKCE flow to this setup. - -### Step 1 — Start PKCE flow inside the WebView +### Step 1 — Start flow inside the WebView Your web code (running inside WebView) does: ```ts -const { codeVerifier, codeChallenge } = await generatePKCE(); -localStorage.setItem('pkce_verifier', codeVerifier); - const params = new URLSearchParams({ client_id: GOOGLE_CLIENT_ID, redirect_uri: 'com.compassmeet://auth', // your deep link response_type: 'code', scope: 'openid email profile', - code_challenge: codeChallenge, - code_challenge_method: 'S256', }); window.open(`https://accounts.google.com/o/oauth2/v2/auth?${params}`, '_system'); @@ -108,20 +101,8 @@ protected void onNewIntent(Intent intent) { super.onNewIntent(intent); String data = intent.getDataString(); - if (data != null && data.startsWith("com.compassmeet://auth")) { - bridge.triggerWindowJSEvent("oauthRedirect", data); - } -} -``` - -Or in Kotlin: -```kotlin -override fun onNewIntent(intent: Intent) { - super.onNewIntent(intent) - val data = intent.dataString - if (data != null && data.startsWith("com.compassmeet://auth")) { - bridge.triggerWindowJSEvent("oauthRedirect", data) - } + String payload = new JSONObject().put("data", data).toString(); + bridge.getWebView().post(() -> bridge.getWebView().evaluateJavascript("oauthRedirect(" + payload + ");", null)); } ``` @@ -129,7 +110,7 @@ That line emits a custom JavaScript event inside the WebView so your web app can --- -### Step 4 — WebView catches redirect event and exchanges the code +### Step 4 — WebView catches redirect event and exchanges the code in backend In your web app (TypeScript side): @@ -137,25 +118,30 @@ In your web app (TypeScript side): window.addEventListener('oauthRedirect', async (event: any) => { const url = new URL(event.detail); const code = url.searchParams.get('code'); - const codeVerifier = localStorage.getItem('pkce_verifier'); - const tokenResponse = await fetch('https://oauth2.googleapis.com/token', { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ - client_id: GOOGLE_CLIENT_ID, - code, - code_verifier: codeVerifier!, - redirect_uri: 'com.compassmeet://auth', - grant_type: 'authorization_code', - }), - }); - - const tokens = await tokenResponse.json(); - console.log('Tokens:', tokens); + // fetch backend API + const tokens = await api('...', {code}) }); ``` +Backend endpoint +```ts +const tokenResponse = await fetch('https://oauth2.googleapis.com/token', { +method: 'POST', +headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, +body: new URLSearchParams({ + client_id: GOOGLE_CLIENT_ID, + code, + redirect_uri: 'com.compassmeet://auth', + grant_type: 'authorization_code', +}), +}); + +const tokens = await tokenResponse.json(); +console.log('Tokens:', tokens); +``` + + At this point: * You have your `access_token` and `id_token`. @@ -166,9 +152,7 @@ At this point: ## 4. Why this works and what makes it safe * The login itself happens in Google’s **system browser**, not in your WebView. -* The `code_verifier` ensures that only your app (which generated the challenge) can exchange the code. * The deep link ensures the token is delivered **only** to your app. -* No backend is required. --- @@ -187,10 +171,10 @@ However, universal links are more setup-heavy (require hosting a `.well-known/as ## 6. Summary -| Step | What happens | Where | -| ---- |----------------------------------------------------------------| -------------- | -| 1 | Generate PKCE challenge and open Google OAuth URL | WebView | -| 2 | User signs in | System browser | -| 3 | Browser redirects to deep link (e.g. `com.compassmeet://auth`) | OS → App | -| 4 | App intercepts deep link and injects it into WebView | Native layer | -| 5 | WebView exchanges `code` for tokens via PKCE | Web app | +| Step | What happens | Where | +| ---- | -------------------------------------------------------------- | ----------------- | +| 1 | Open Google OAuth URL | WebView | +| 2 | User signs in | System browser | +| 3 | Browser redirects to deep link (e.g. `com.compassmeet://auth`) | OS → App | +| 4 | App intercepts deep link and injects it into WebView | Native layer | +| 5 | WebView exchanges `code` with backend for tokens | Web app + backend |