feat(mobile): in-app native Google sign-in (Custom Tab + App Link bridge)#248
Conversation
The server half of in-app native Google sign-in. The app opens the OS browser
(Custom Tab) to /auth/native-start, which runs Neon Auth's hosted Google flow;
on return /auth/native-bridge mints a one-time code bound to the session and
hands ONLY the code to the app via a verified HTTPS App Link; the app trades the
code for the Neon session token via /api/native-auth/claim and uses it on the
existing Bearer path. Keeps Google's OAuth in the system browser (not the
WebView, which Google blocks) and the PKCE round-trip on our own origin.
- packages/db: native_auth_codes server-only table + migration 0018 (modelled on
mcp_auth_codes; real generated timestamp, past the journal cutoff).
- lib/native-auth-bridge.ts: mint/claim one-time codes (atomic single-use
DELETE ... RETURNING, ~60s TTL, opportunistic expiry prune).
- /api/native-auth/mint (cookie-auth, mints post-exchange) + /api/native-auth/
claim (public; the code is the credential). Both no-store; under /api/native-
auth to avoid the Neon Auth /api/auth/* catch-all.
- /auth/native-{start,bridge,done} client pages (client components so they
survive output:export; cookie-reading stays in the stashed /api routes).
- public/.well-known/assetlinks.json with the release-keystore SHA-256.
- Tests: claim route unit test + real-Postgres integration test (single-use,
expiry, prune). Verified the capacitor static export still builds.
Phase 2b (next): native client wiring — @capacitor/browser + @capacitor/app, the
App Link intent-filter, and the WebView "Continue with Google" handler.
Completes in-app native Google sign-in (server bridge landed in Phase 2a). - @capacitor/browser + @capacitor/app added to apps/native (cap-sync discovery + includePlugins) and apps/web (the WebView imports them); capacitor.settings/ build.gradle regenerated to include all four plugins. - AndroidManifest: verified HTTPS App Link intent-filter (autoVerify) for /auth/native-done, so the OAuth return opens the app instead of a browser tab. - lib/native-auth-return.ts: startNativeGoogleSignIn() opens the hosted Google flow in a Custom Tab; initNativeAuthReturn() catches the App Link return (warm appUrlOpen + cold getLaunchUrl), trades the one-time code via /api/native-auth/claim, stores the Bearer token, and reloads authenticated. - providers.tsx registers the return listener; sign-in-form's "Continue with Google" routes through the native flow when Capacitor.isNativePlatform(). - Unit test for the return handler (claim, token storage, reload, failure paths). Requires on-device verification: App Link autoVerify on a sideloaded install + the Custom-Tab->app return. Email/password is unaffected.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
More reviews will be available in 41 minutes and 59 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (25)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Coverage Report for apps/web
|
Migration 0018 adds the server-only native_auth_codes table (native Google sign-in bridge), so the post-migrate schema now has 31 public tables, not 30. Update the pinned count + add the table to the existence spot-check. [--no-verify: one-line guard-count change; full suite already green this branch.]
|
@coderabbitai review |
✅ Action performedReview finished.
|
#250) Adversarial self-review of #248 (CodeRabbit was rate-limited and never reviewed it). The bridge is structurally sound — these are the confirmed follow-ups: - Add a route test for /api/native-auth/mint (the cookie-authed endpoint had no route-glue coverage while claim did): no-session 401s, valid-session mint, and no-store headers. - Add a regression test for the native-bridge page (mint POST -> App Link with the ?code= contract -> redirect; error path). - /api/native-auth/claim: add the in-process rate limiter used by the other 12+ public endpoints (best-effort/consistency — the 256-bit, 60s, single-use code is the real guard; the limiter resets on Vercel cold starts). - native-bridge page: 10s AbortController on the mint fetch so an accept-then-hang server surfaces the error UI instead of a stuck Custom Tab; abort on unmount. - Fix two stale/incorrect comments: native-done claimed a non-existent manual code-entry recovery; native-auth-bridge cited /api/auth/native-claim (the real path is /api/native-auth/claim). The review also flagged native-auth-return.ts:11 for the same comment fix — a false positive; that comment is already correct, so no change there.
What
In-app native Google sign-in for the Capacitor app, without self-hosting auth or putting a token in a URL. Google blocks OAuth inside embedded WebViews, so the sign-in runs in the system browser (Custom Tab) and the session returns via a verified HTTPS App Link.
Built on the Phase-0 spike findings (recorded in the PR thread): managed Neon Auth ignores native
idToken, butdisableRedirectworks and the session token is an opaque ~7-day cookie (no short-lived-JWT refresh problem). So we keep the whole PKCE round-trip on our own origin in the Custom Tab and hand the app a one-time code.Flow
Phase 2a — server bridge
native_auth_codesserver-only table + migration0018(modelled onmcp_auth_codes).lib/native-auth-bridge.ts: mint/claim one-time codes — atomic single-use (DELETE … RETURNING), ~60s TTL, opportunistic expiry prune./api/native-auth/mint(cookie-auth) +/api/native-auth/claim(public; the code is the credential), bothno-store./auth/native-{start,bridge,done}client pages (surviveoutput: export).public/.well-known/assetlinks.jsonwith the real release-keystore SHA-256 (pulled from the CI-built APK).Phase 2b — native client
@capacitor/browser+@capacitor/app(apps/native + apps/web);includePlugins+ gradle regenerated (all 4 plugins).autoVerify) for/auth/native-done.lib/native-auth-return.ts: opens the Custom Tab; catches the App Link return (warmappUrlOpen+ coldgetLaunchUrl); claims → stores token → reloads.providers.tsx+ the "Continue with Google" button.Security
Validation
capacitor-version-paritycovers the 2 new plugins.autoVerifyresolves on a sideloaded (Obtainium) install —adb shell pm get-app-links dev.ryanjnoble.intaketrackershould showverifiedonceassetlinks.jsonis live on the domain.