All articles

Technical

Implementing WebAuthn and Passkeys in an Android TWA

September 19, 2026 · 7 min read

Passkeys and passwordless authentication are rapidly replacing legacy username-and-password systems. For web developers deploying a Progressive Web App (PWA) to the Google Play Store via a Trusted Web Activity (TWA), a key question arises: can you use WebAuthn and biometric passkeys inside the wrapped app? The answer is yes, and because a TWA runs within a fully-featured browser engine, the implementation process is highly secure and straightforward.

Understanding WebAuthn in the Context of TWAs

Web Authentication (WebAuthn) is a web standard that allows servers to register and authenticate users using asymmetric cryptography. Instead of passwords, users authenticate using security keys, platform authenticators such as Windows Hello, Apple FaceID/TouchID, or on mobile, Android Fingerprint and Face Unlock.

Unlike legacy WebView-based wrappers, which completely block access to WebAuthn APIs due to security origins and lack of integration with the platform credential manager, a TWA has a direct bridge to the underlying system browser. When a user runs your TWA app on Android, the engine powering the app is Chrome or another default Custom Tabs provider. This engine has direct access to the Android Credential Manager, enabling native biometric dialogs to render seamlessly over your web content.

The Critical Role of Digital Asset Links

Before writing any JavaScript code, you must satisfy the absolute requirement for WebAuthn within a TWA: Digital Asset Links. When you perform WebAuthn operations, the Android operating system and the web browser verify the origin of the request. For security reasons, passkeys are tied strictly to a specific domain (e.g., app.example.com).

If your TWA is launched, it runs in a verified domain context. If Digital Asset Links are not properly configured, Chrome will render a URL bar at the top of the screen, and the operating system may refuse to complete biometric authentication because it cannot establish a secure link of trust between your native Android APK and your web origin. The assetlinks.json file on your server must associate your SHA-256 certificate fingerprint with your package name, ensuring that the Android operating system knows your native app wrapper has the authority to act on behalf of your web origin.

Implementing Passkey Registration

To register a passkey inside your TWA, you must call the navigator.credentials.create API. The server must first generate a set of creation options. These options must contain a challenge, user details, and relying party parameters. Below is an example of how you handle the registration flow in your PWA client code.

const credentialCreationOptions = { publicKey: { challenge: Uint8Array.from("RANDOM_CHALLENGE_FROM_SERVER", c => c.charCodeAt(0)), rp: { name: "Your TWA App Name", id: "app.example.com" }, user: { id: Uint8Array.from("USER_ID_123", c => c.charCodeAt(0)), name: "user@example.com", displayName: "User Name" }, pubKeyCredParams: [{ demo: -7, type: "public-key" }], authenticatorSelection: { authenticatorAttachment: "platform", userVerification: "required", residentKey: "required" }, timeout: 60000 } }; navigator.credentials.create(credentialCreationOptions) .then((credential) => { console.log("Passkey registered successfully", credential); }) .catch((err) => { console.error("Registration failed", err); });

When this code executes inside your TWA, the Android system intercepts the request and presents the native Android lock screen or biometric verification prompt. This feels completely indistinguishable from a native Android application.

Implementing Passkey Authentication

To authenticate a returning user, your PWA will request an assertion from the credential manager using the navigator.credentials.get API. The server must provide a new challenge to prevent replay attacks. Here is the implementation details for the assertion phase:

const credentialRequestOptions = { publicKey: { challenge: Uint8Array.from("NEW_CHALLENGE_FROM_SERVER", c => c.charCodeAt(0)), rpId: "app.example.com", userVerification: "required", timeout: 60000 } }; navigator.credentials.get(credentialRequestOptions) .then((assertion) => { console.log("User authenticated successfully", assertion); }) .catch((err) => { console.error("Authentication failed", err); });

The assertion object returned contains a signature that must be validated on your backend using the public key that was saved during the registration phase. If validated, the user is safely logged in.

Comparison of Authentication Methods in TWAs

Understanding which authentication route to take is critical when designing a TWA. The table below outlines how WebAuthn compares to traditional strategies:

Authentication TypeUser ExperienceSecurity LevelOffline Compatibility
WebAuthn / PasskeysExcellent (Single biometric tap)Highest (Cryptographically backed, phishing-resistant)Excellent (Credentials stored on device)
OAuth Redirects (Google/Apple)Good (Redirect loop inside TWA)High (Managed by external provider)Poor (Requires active internet connection)
Session CookiesAverage (Invisible but manual initial login)Medium (Vulnerable to session hijacking)Good (Persisted across launches)

Handling Fallbacks and Device Compatibility

While the vast majority of modern Android devices running Android 9 and newer support WebAuthn and Passkeys, you should always design a progressive enhancement strategy. If a device does not have biometric hardware, or if the user has disabled lock screen security, calling the API directly can result in an error.

Before displaying a passkey login button inside your TWA UI, verify whether the system supports it by calling the PublicKeyCredential helper method:

if (window.PublicKeyCredential) { PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() .then((available) => { if (available) { console.log("Passkeys are supported on this Android device"); } else { console.log("Passkeys not supported, falling back to legacy login"); } }); } else { console.log("WebAuthn not supported by the underlying browser engine"); }

By checking this availability ahead of time, you can conditionally show or hide your passkey login options and ensure that users on older Android versions are not presented with broken controls.

Securing Your Implementation Against Verification Failures

One security concern specific to hybrid web apps is ensuring that your origin matches the origin defined in your Google Play developer profile. Because WebAuthn binds credentials strictly to the origin domain (rpId), any mismatch in your TWA setup will cause credential operations to throw a SecurityError. Double check that you are testing on a production-ready domain and that you have fully signed your TWA app using your active release keystore. Local debug keys will have a different SHA-256 fingerprint, meaning you must add both debug and release SHA fingerprints to your Digital Asset Links file during development and testing phases.

Ready to ship your Android app?

Paste your PWA URL, get a signed APK and a Google Play ready AAB in minutes.

Build my app