Sign-in works differently inside the native shells than it does on the web, for one specific
reason: Google actively blocks OAuth flows from completing inside an embedded web view. Everything
else about how the shells handle auth follows from that one constraint.

## Magic codes work exactly as they do on the web

A magic **code**, the six-digit number you enter after requesting a sign-in, works fine inside the
shell's embedded web view with no special handling at all. There's no redirect involved, no
external identity provider, just a form submission the web view handles like any other page. This
is different from a magic *link*, which would need to reopen the app from an email client; codes
sidestep that problem entirely, which is part of why the kit uses codes rather than links in the
first place.

## Social sign-in has to leave the web view

Google and Apple sign-in are different. Google in particular treats an embedded web view as
untrustworthy for OAuth and blocks the flow outright, so both shells open social sign-in
**externally** instead of inside the app's own web view: `ASWebAuthenticationSession` on iOS,
Chrome Custom Tabs on Android. The user completes sign-in in that external, trusted browser
context, and the shell receives the resulting session back and hands it to the web view.

This is driven by the path configuration Rails serves at `/configurations/ios.json` and
`/configurations/android.json`: every `/auth/` route is marked `external`, which tells each shell
to intercept navigation to that path rather than load it in the embedded web view. Because this
routing rule lives in a Rails-served configuration file rather than being compiled into the app,
changing which paths open externally doesn't need a new build or a new App Review.

## Apple sign-in is iOS-only in the shells

The server gates the Sign in with Apple button on `hotwire_native_android?`, so it doesn't render
inside the Android shell at all. Apple only requires SIWA to be offered on iOS; showing it on
Android would be both unnecessary and slightly odd for an Android user.

## What this means if you add a new OAuth provider

Any new social sign-in provider needs the same treatment: its `/auth/:provider` path has to be
marked `external` in the path configuration, or the shell will try to load the provider's
consent screen inside the embedded web view and it may silently fail to complete, the same failure
mode Google's blocking produces.

## Next

See how the shells handle the one thing that can't route through a webview at all, paid
subscriptions: [In-App Purchases](/docs/mobile-apps/in-app-purchases).
