If a meaningful share of your traffic arrives from social feeds, some of your consumers never reach your login page in a real browser. They tap a link inside Instagram, TikTok, Facebook or X and your page opens inside the app's in-app browser - an embedded WebView. That environment quietly breaks the login method you most want them to use: passkeys.
This hurts most when you have meaningful social ad spend. You are paying for every tap on that Instagram, TikTok or Facebook ad, and the whole point is that those users land, sign up and convert. If they drop inside the in-app browser before they can authenticate, you are burning ad budget on traffic that never had a working path to log in - so the friction is not just a UX problem, it is wasted acquisition cost.
The failure is almost invisible. The consumer does not file a ticket saying "the passkey button did nothing inside Instagram." They bounce, and in your funnel it looks like generic login friction. Below: the exact technical reasons passkeys break in in-app browsers, why standard analytics cannot see it, how to detect the environment reliably and what to do so those users still get in.
FBAN/FBAV for
Facebook, Instagram, BytedanceWebview/musical_ly for TikTok)When a user taps a link inside a social app, the app does not hand them off to Safari or
Chrome. It renders the page inside an embedded WebView that lives in the host app's
process. On iOS that is WKWebView; on Android it is the System WebView component. The
host app, not the user, decides what that WebView is allowed to do.
This matters because there is a second, very different way an app can show web content. The comparison below contrasts the two containers - and that difference is the whole story:
An embedded WebView (WKWebView, Android System WebView) runs inside the host app with an
isolated cookie/session store, hides the URL bar and exposes a limited and inconsistent set of
browser capabilities - this is what most social feeds use for their in-app browser. A
system-browser handoff (SFSafariViewController / ASWebAuthenticationSession on iOS,
Chrome Custom Tabs on Android) instead uses the real browser engine, shares the system
session, shows the URL with SSL indicators and supports WebAuthn - the trustworthy path for
authentication.
The whole problem comes down to the first category. Passkeys assume real-browser WebAuthn capabilities and an association with your own domain. An embedded WebView gives you neither.
Passkeys depend on WebAuthn, and WebAuthn depends on browser and platform capabilities that embedded WebViews do not reliably provide. The picture is more nuanced than "WebViews never support WebAuthn": modern in-app browsers often attempt WebAuthn, but the behaviour is inconsistent and breaks in ways that are hard to see.
WebAuthn support is inconsistent. An embedded WebView may not expose a fully functional WebAuthn API at all, and even where it does, a passkey ceremony can throw an error or silently do nothing. The host app - not a real browser engine - controls the WebView, so there is no guarantee it implements the full ceremony the way Safari or Chrome does. The practical takeaway: a passkey path that works in the system browser cannot be assumed to work inside a host app's WebView.
Conditional UI does not work. Conditional UI - the passkey autofill that surfaces credentials directly in the username field - is not supported inside a WebView. So even the smoothest passkey entry point you have built is dead there.
Passkeys are scoped to the host app, not your site. On iOS, using passkeys inside a WKWebView requires Associated Domains entitlements and an AASA file - and those belong to the host app (Instagram, TikTok), not to you, the relying party. A consumer arriving at your domain inside someone else's app cannot use your passkeys, because the WebView is not associated with your domain.
The net effect: to the consumer it looks like a broken button; to your funnel it looks like an unexplained abandon. The fast, passwordless login you would normally lean on is precisely the one that breaks here, pushing the consumer toward passwords or OTP, the slowest and most abandon-prone routes.
Social login degrades in the same in-app-browser environments too, for separate reasons - we cover that in detail in social login conversion rate.
In-app browsers are not always a dead end anymore. At Identiverse, PayPal presented a
workaround built together with Meta: when a Meta app's WebView redirects to paypal.com,
the host app conditionally injects a WebAuthn-compliant custom credentials API into the
in-app browser, so navigator.credentials becomes available where it normally is not. That
injected API talks directly to the device hardware to create and store the key pair, making
it a real, hardware-backed - but device-bound - passkey.
It works, but the trade-offs are significant:
paypal.com passkey) is conceivable. The protection
is server-side: the relying party still verifies every
assertion, so a forged credential fails verification. Note this is not OAuth - PayPal acts
as its own identity provider here.For most teams this is not a drop-in fix: it needs host-app cooperation plus a custom, standards-compliant integration - the same approach PayPal demonstrated at Authenticate 2025. But it is a useful signal of where the ecosystem is heading - and turns "WebViews break passkeys" into "WebViews break passkeys unless the host app explicitly bridges them."
This is the part that makes in-app-browser friction so dangerous: it is structurally invisible to the tooling most teams trust.
A passkey ceremony that never starts, or fails silently on the client, produces no successful backend call. Your identity provider logs and server-side dashboards only record requests that reach them. The failure happens one layer earlier, on the device, so it leaves no trace in the place you are looking.
This is the same gap that authentication observability exists to close: the difference between "was the login successful?" and "how was it successful, and if it failed, where and why?" Without client-side telemetry, in-app-browser abandons are silently bucketed into generic drop-off, and you optimize everything except the actual cause.
The good news: in-app browsers identify themselves. The host app appends recognizable
tokens to the User-Agent string, so you can detect the environment on page load, before
you render the login options.
The exact tokens vary by app and platform, but the major social and messaging apps each leave a recognizable fingerprint. The most useful ones to match:
FBAN, FBAV, FB_IAB, FBIOS, FB4AMessengerForiOS, Orca-AndroidInstagramBarcelona (Threads' internal codename in the UA)BytedanceWebview, musical_ly, Trill, TikTok, awemeTwitter, TwitterAndroidLinkedInAppSnapchatPinterestRedditWhatsAppMicroMessengerLine/TelegramKAKAOTALKWeiboNAVERbaiduboxappGSAA more complete detection helper, covering the apps above and returning which one was matched so you can segment by it later:
const IN_APP_BROWSER_RULES = [ { name: 'instagram', test: /Instagram/i }, { name: 'messenger', test: /Messenger(ForiOS)?|Orca-Android/i }, { name: 'threads', test: /Barcelona/i }, { name: 'facebook', test: /FBAN|FBAV|FB_IAB|FBIOS|FB4A/i }, { name: 'tiktok', test: /BytedanceWebview|musical_ly|Trill|TikTok|aweme/i }, { name: 'twitter', test: /Twitter|TwitterAndroid/i }, { name: 'linkedin', test: /LinkedInApp/i }, { name: 'snapchat', test: /Snapchat/i }, { name: 'pinterest', test: /Pinterest/i }, { name: 'reddit', test: /Reddit/i }, { name: 'whatsapp', test: /WhatsApp/i }, { name: 'wechat', test: /MicroMessenger/i }, { name: 'line', test: /\bLine\//i }, { name: 'telegram', test: /Telegram/i }, { name: 'kakaotalk', test: /KAKAOTALK/i }, { name: 'weibo', test: /Weibo/i }, { name: 'naver', test: /NAVER/i }, { name: 'baidu', test: /baiduboxapp/i }, { name: 'google', test: /\bGSA\b/i }, ]; function getInAppBrowser(ua = navigator.userAgent) { return IN_APP_BROWSER_RULES.find(rule => rule.test.test(ua))?.name ?? null; }
Order the rules from most to least specific: Instagram, Messenger and Threads are checked
before generic Facebook tokens because Meta apps share parts of the FBAN/FBAV family,
and you want the more precise label.
User-Agent matching is heuristic, not perfect, so two rules apply. First, maintain it:
host apps change their strings over time, so treat the list as something you review, not
set-and-forget. Second, measure it: tag every login session with the detected in-app
browser (or null) and feed that tag into your analytics so the failure mode finally
becomes a number you can see. Two signals are especially diagnostic when segmented by this
tag:
You cannot make social apps fix their WebViews, so every durable fix is on your side.
SFSafariViewController /
ASWebAuthenticationSession
on iOS or Chrome Custom Tabs on Android, which use the real browser engine and
support WebAuthn. The native app passkeys guide covers this
in depth.Two of the hard parts here are visibility and routing, and both map to what Corbado does. Corbado Observe provides the client-side telemetry to segment login outcomes by browser, OS and entry point - so in-app-browser friction shows up as a measurable cohort instead of vanishing into generic drop-off. On the routing side, detecting the environment and presenting the method most likely to succeed for that session is exactly the kind of data-driven decision passkey-first login should be making.
If you are running passkeys at consumer scale and want to see where logins quietly fail before they reach your backend, this is the visibility layer that surfaces it.
In-app browsers are a structural, not occasional, source of login failure for any product with social traffic, and they specifically break passkeys - the method you would otherwise rely on as your fast path. Passkeys fail because WebViews lack reliable WebAuthn and Conditional UI and are not associated with your domain. The failures happen on the client, so backend logs never see them.
The fix is a sequence: gain visibility by tagging in-app-browser sessions, then handle them gracefully by detecting the environment and routing users to a method that works, with a clear path back to a real browser. Teams that do neither keep losing conversions to a failure mode they cannot even see.
Often not reliably. Social-app in-app browsers are embedded WebViews (WKWebView on iOS,
Android System WebView), where WebAuthn behaviour is inconsistent and Conditional UI (passkey
autofill) is not supported. A passkey ceremony may throw an error or silently do nothing, so
the consumer sees a button that appears to do nothing.
Inspect the User-Agent string for host-app tokens: FBAN, FBAV and FB_IAB for
Facebook, Instagram for the Instagram app and BytedanceWebview, musical_ly or trill
for TikTok. Tag every login session with whether it originated in an in-app browser so the
failure mode becomes visible in your analytics.
An embedded WebView (WKWebView, Android System WebView) runs inside the host app with an
isolated session and limited browser features. Chrome Custom Tabs on Android and
SFSafariViewController / ASWebAuthenticationSession on iOS use the real browser engine,
share the system session and support WebAuthn, so passkeys work as expected.
Corbado is the Passkey Intelligence Platform for CIAM teams running consumer authentication at scale. We help you see what IDP logs and generic analytics tools can't: which devices, OS versions, browsers and credential managers support passkeys, why enrollments don't turn into logins, where the WebAuthn flow fails and when an OS / browser update silently breaks login, all without replacing Okta, Auth0, Ping, Cognito or your in-house IDP. Two products: Corbado Observe layers observability for passkeys and any other login method. Corbado Connect adds managed passkeys with analytics built in (alongside your IDP). VicRoads runs passkeys for 5M+ users with Corbado (+80% passkey activation). Talk to a Passkey Expert →
Related Articles
Table of Contents