Meet Corbado at Identiverse 2026 - Las Vegas, June 16Las Vegas
Back to Overview

Passkeys in In-App Browsers: Why Logins break and how to fix it

Passkeys often break inside social-app in-app browsers (Instagram, TikTok, Facebook). Learn why it happens, how to detect WebView traffic and how to route users to a method that works.

Vincent Delitz
Vincent Delitz

Created: June 22, 2026

Updated: June 22, 2026

Passkeys in In-App Browsers: Why Logins break and how to fix it

1. Introduction#

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.

Key Facts
  • In-app browsers are embedded WebViews (WKWebView on iOS, Android System WebView), not the system browser - they run inside the host app and inherit its restrictions
  • WebAuthn behaviour inside embedded WebViews is inconsistent and subject to platform-specific bugs, and Conditional UI (passkey autofill) is not supported in a WebView - so passkey prompts can silently fail to appear
  • On iOS, WKWebView uses an isolated cookie store separate from Safari and requires Associated Domains plus an AASA file to use passkeys at all - a third-party site's passkeys generally will not work inside another app's WebView
  • In-app browsers are detectable from the User-Agent (e.g. FBAN/FBAV for Facebook, Instagram, BytedanceWebview/musical_ly for TikTok)
  • These failures happen on the client before any request reaches your backend, so backend logs cannot see them - this is the same blind spot authentication observability is built to close

2. What an In-App Browser actually is#

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.

3. Why Passkeys break inside In-App Browsers#

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.

4. An emerging exception: app-injected device-bound passkeys#

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:

  • No sync (today). The credential started out device-bound, exactly as WebAuthn Level 1 was: it never reaches iCloud Keychain or Google Password Manager and does not follow the user to another device. Broader cloud-backed support is a stated direction, not the current state.
  • A separate passkey per host app. Each app registers its own credential, tied to that app's identifier - so the passkey created inside Instagram's WebView is not the one inside Facebook's WebView, even on the same device. The user ends up with one silo per app.
  • Enrollment first, every time. Because nothing is synced, the user has to create the passkey in each app context, so the core synced-passkey promise ("you already have it everywhere") does not apply.
  • Trust shifts to the host app. Since the host app injects the credentials API, a fake implementation (for example a forged 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."

5. Why your Analytics cannot see it#

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.

6. How to detect In-App Browser traffic#

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:

  • Facebook: FBAN, FBAV, FB_IAB, FBIOS, FB4A
  • Facebook Messenger: MessengerForiOS, Orca-Android
  • Instagram: Instagram
  • Threads: Barcelona (Threads' internal codename in the UA)
  • TikTok: BytedanceWebview, musical_ly, Trill, TikTok, aweme
  • X (Twitter): Twitter, TwitterAndroid
  • LinkedIn: LinkedInApp
  • Snapchat: Snapchat
  • Pinterest: Pinterest
  • Reddit: Reddit
  • WhatsApp: WhatsApp
  • WeChat: MicroMessenger
  • LINE: Line/
  • Telegram: Telegram
  • KakaoTalk: KAKAOTALK
  • Weibo: Weibo
  • Naver: NAVER
  • Baidu: baiduboxapp
  • Google app (GSA): GSA

A 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:

  • Method switching - users who start with a passkey and then fall back to password or OTP within the same session. A concentration of switching inside in-app sessions is the fingerprint of this problem.
  • Drop-off by entry touchpoint - if sessions referred from social apps convert far worse than direct traffic, in-app browsers are a prime suspect.

7. What to do about it#

You cannot make social apps fix their WebViews, so every durable fix is on your side.

  1. Detect early, before rendering the login UI. Run the User-Agent check on load so you can adapt the screen for in-app sessions instead of showing controls that will fail.
  2. Do not lead with a method that cannot work. If you detect a true embedded WebView, deprioritize the passkey button for that session and lead with a method that works reliably there, such as an email magic link or OTP. The consumer should never be left staring at a dead button. (Keep a graceful path back to passkeys once they are in a capable browser - see passkey fallback and recovery.)
  3. Offer "Open in browser." Most in-app browsers expose an "open in Safari/Chrome" affordance. A small, well-placed prompt to open the page in the real browser restores full passkey support.
  4. If the traffic is from your own app, use the right container. When you control the app, never wrap authentication in a bare WebView. Use 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.
  5. Measure the before/after. Track in-app-browser login success as its own metric so you can prove a fix moved the number rather than guessing, and so any future regression in this environment surfaces as a sudden drop instead of going unnoticed.

8. How Corbado helps#

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.

9. Conclusion#

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.

FAQ#

Do passkeys work in in-app browsers like Instagram or TikTok?#

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.

How do I detect if a user is in an in-app browser?#

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.

What is the difference between an in-app browser and Chrome Custom Tabs?#

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

About Corbado

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

See what's really happening in your passkey rollout.

Explore the Console

Share this article


LinkedInTwitterFacebook