Join our upcoming Webinar on Passkeys for Australian Enterprises
webauthn origin validation native apps

WebAuthn Origin Validation in the Context of Native Apps

Learn how origin validation works in WebAuthn for native iOS and Android apps - incl. Android SHA-256 fingerprints, AASA files and server-side trust.

Blog-Post-Author

Amine

Created: April 24, 2025

Updated: April 30, 2025


1. Introduction#

When implementing passkeys with WebAuthn, validating the origin is a critical security step. It ensures your users authenticate only through trusted apps or websites. But here's the catch: validating origins becomes particularly challenging when dealing with native mobile apps. Unlike web apps, native apps don't naturally have a URL-based origin, making the validation process both different and potentially tricky.

This raises two fundamental questions that we'll tackle in this post:

  1. Why is origin validation different (and potentially tricky) for native apps compared to web apps?
  2. Which specific mechanisms ensure that the server trusts the app's origin on both Android and iOS?

Before we dive deeper, a quick context check: this article assumes you're already familiar with basic WebAuthn concepts — particularly the roles of the Relying Party (RP) and Relying Party ID (RPID). If you're new to these concepts or need a refresher, check out our previous blog post that covers the foundational elements of WebAuthn.

Ready? Let's dig in!

2. What Is an Origin in General?#

2.1 Definition#

In web development, the term origin refers to the unique combination of the protocol, domain, and port from which content is served. It’s essentially a security boundary for web browsers, defining how web applications interact with each other (for a detailed definition, see MDN’s Glossary on Origin).

Example: https://example.com

Note: the protocol/domain/port origin model applies only to web apps.
For native apps, origin is set up differently—as we explain in the next sections.

2.2 Origin in WebAuthn#

In WebAuthn, the concept of origin is essential for ensuring authenticity and security. When a passkey is created or used, the clientDataJSON—sent from the user's device to your server—explicitly includes the origin. This helps your server verify that the authentication request originated from a trusted source.

Here's a simplified example of how the origin might appear within the clientDataJSON:

{ "type": "webauthn.create", "challenge": "rGhL28M9u...", "origin": "https://example.com", "crossOrigin": false }

To inspect and understand exactly how this data looks in real-world scenarios, consider using the Corbado Passkeys Debugger, which decodes and clearly displays clientDataJSON:

clientData Json

2.3 Origin in Native Apps#

Native apps typically don't have a conventional origin like web apps (e.g. https://www.example.com). This absence of a standard, URL-based origin means specialized validation measures are required on platforms such as Android and iOS, which we'll explore in detail in the upcoming sections.

3. What Origin Is Used in an Android App?#

Since native Android apps don't have a traditional URL-based origin, Android relies on the SHA‑256 digest of the APK’s signing certificate ("fingerprint") — as the foundation for origin validation. You can obtain the fingerprint with:

keytool -list -v -keystore my-release-key.jks | grep SHA256:

For instance, a typical SHA-256 fingerprint looks like this:

8B:BF:39:60:61:89:30:A4:45:F3:D7:09:1E:7B:1B:05:0F:8A:FD:AF:24:EB:F1:EB:2E:3D:13:88:09:FC:79:59

To generate this fingerprint for your Android app, refer to platform-specific guides such as the official Flutter documentation or the Android developer documentation.

Before using this fingerprint for forming the final WebAuthn origin, you need to transform it by hex decoding the SHA-256 fingerprint and then base64 encoding the result without padding.

Here's a JavaScript snippet demonstrating this transformation:

function hexToBase64NoPadding(hexWithColons) { const hex = hexWithColons.replace(/:/g, ""); const buffer = Buffer.from(hex, "hex"); return buffer.toString("base64").replace(/=+$/, ""); }

You can adapt this logic to other languages easily (consider using ChatGPT or your preferred tool for translation).

If we apply this transformation to the example fingerprint provided earlier, we get the following base64-encoded string without padding:

i78xYGGJMKRF89cJHnsbBQ+K/a8k6/HrLj0TiAn8eVk

The origin string format for Android is as follows:

android:apk-key-hash:<base64-string-without-padding-of-fingerprint>

The resulting final origin string, based on the example fingerprint, then is:

android:apk-key-hash:i78xYGGJMKRF89cJHnsbBQ+K/a8k6/HrLj0TiAn8eVk

This is the origin that your server must trust and validate when processing WebAuthn registrations or logins from an Android app.

4. What Origin Is Used in an iOS App?#

Unlike Android, native iOS apps leverage the RPID directly to form the WebAuthn origin: https:// + RPID

Since iOS apps don't have a browser-based origin, trust must be explicitly established between the app and the domain identified by the RPID through Apple's standardized mechanism known as the Apple App Site Association (AASA) file.

To validate your app’s origin on iOS, you must serve an Apple App Site Association file from your domain. For example, if your RPID is example.com, the AASA file must be accessible via:

https://example.com/.well-known/apple-app-site-association

This JSON file explicitly associates your iOS app with your domain, allowing the server-side verification to recognize and trust requests originating from the native app.

If you're new to configuring this file or need additional details, refer back to our previous blog post covering Apple App Site Association in more depth.

5. How Is Origin Validation Handled on the Server Side?#

Once a user completes a passkey registration or login using a native app, the WebAuthn ceremony sends a payload back to your server. One of the critical components in this payload is the clientDataJSON object — and this is where origin validation begins.

5.1 Server-Side Verification#

When your server receives a WebAuthn request (for either registration or authentication), it extracts and parses the clientDataJSON. Among other metadata, this JSON includes the origin — essentially, the identity of the app or website that initiated the request.

Your job as a relying party is to validate that this origin matches one of the origins you explicitly trust. If the origin doesn’t match, the request must be rejected to prevent spoofing or phishing attempts.

5.2 Remember: Multiple Origins#

Native apps complicate things a bit because — as you've seen — Android and iOS each have their own way of representing origins:

  • Android uses android:apk-key-hash:<base64-string-without-padding-of-fingerprint> for its origin (e.g. android:apk-key-hash:i78xYGGJMKRF89cJHnsbBQ+K/a8k6/HrLj0TiAn8eVk)
  • iOS constructs its origin using the RPID in the format https:// + RPID (e.g. https://example.com)

Therefore, you must configure both origins on your server — one for each platform — to support passkey flows across iOS and Android.

5.3 Configure Multiple Origins#

To assist you in setting up your server-side library (relying party), we provide guidance on configuring multiple origins for the following libraries and languages:

Go: go-webauthn

webauthnConfig := &webauthn.Config{ RPDisplayName: "Example App", RPID: "example.com", RPOrigins: []string{ "https://example.com", // iOS (RPID: example.com) "android:apk-key-hash:i78xYGGJMKRF89cJHnsbBQ+K/a8k6/HrLj0TiAn8eVk", // Android }, }

In go-webauthn, all origins listed in RPOrigins will be matched against the origin sent in clientDataJSON. Even though Android origins are not explicitly mentioned in the docs, the library accepts them as long as they match exactly.

JavaScript/TypeScript: SimpleWebAuthn

const expectedOrigins = [ "https://example.com", // iOS (RPID: example.com) "android:apk-key-hash:i78xYGGJMKRF89cJHnsbBQ+K/a8k6/HrLj0TiAn8eVk", // Android ]; verifyRegistrationResponse({ credential: parsedCredential, expectedChallenge: challenge, expectedOrigin: expectedOrigins, expectedRPID: "example.com", });

SimpleWebAuthn optionally supports verifying registrations from multiple origins and RP IDs! Simply pass in an array of possible origins and IDs for expectedOrigin and expectedRPID respectively.

Relevant docs:

verifyRegistrationResponse
verifyAuthenticationResponse

PHP: web-auth/webauthn-framework

To support native apps in addition to the web origin, you need to extend the origin verification logic. As of version 4.8+, webauthn-framework introduced more flexible input validation to accommodate non-web origins. The maintainers have made it possible to “hook into and whitelist origins” in custom code (GitHub issue).

There isn’t simply a config file where you list allowed origins in webauthn-framework (as of v4.x/5.0), but you can achieve it with a small amount of code:

$allowedOrigins = [ "https://example.com", // iOS (RPID: example.com) "android:apk-key-hash:i78xYGGJMKRF89cJHnsbBQ+K/a8k6/HrLj0TiAn8eVk", // Android ]; $publicKeyCredentialSource = $authenticatorAssertionResponseValidator->check( $credentialId, $authenticatorAssertionResponse, $publicKeyCredentialRequestOptions, $request, $userHandle, $allowedOrigins );

6. Handling Edge Cases#

6.1 Hybrid Applications (WebView Integration)#

Hybrid apps using WebViews require special attention:

6.1.1 Android (WebView + WebAuthn)#

6.1.2 iOS (WKWebView + WebAuthn)#

Note: WebAuthn only works in WebViews when loaded over HTTPS from a verified domain that’s linked to the app via platform trust mechanisms.

6.2 Multiple Build Flavors (Dev, QA, Production)#

Different build environments require distinct configurations:

  • Android: Each environment typically has unique signing certificates, resulting in distinct SHA-256 fingerprints. Your server needs to recognize and validate each of these separately.
  • iOS: Each environment requires a dedicated Apple App Site Association (AASA) file hosted under distinct domains or subdomains.

7. Conclusion#

Origin validation is one of the most critical pieces of the WebAuthn puzzle—especially when extending passkey support to native mobile apps. As we’ve seen, both Android and iOS diverge from the browser-based model, each with their own mechanisms for establishing trust:

  • Android relies on SHA-256 certificate fingerprints to construct a special apk-key-hash origin.
  • iOS constructs its origin using the RPID in the format https:// + RPID, with trust being established via the Apple App Site Association mechanism.

Understanding and correctly validating these origins on the server is essential to prevent spoofed or malicious authentication requests.

7.1 Key Takeaways#

  • WebAuthn origins are not one-size-fits-all. Native apps break away from the traditional https:// origin format.
  • Android uses android:apk-key-hash:<base64-string-without-padding-of-fingerprint> for its origin.
  • iOS constructs its origin using the RPID in the format https:// + RPID, with trust being established via the Apple App Site Association mechanism.
  • Your server must be explicitly configured to trust both origins, using libraries like go-webauthn, SimpleWebAuthn, or web-auth/webauthn-framework depending on your stack.

7.2 What’s Next?#

If you're looking to go deeper:

Add passkeys to your app in <1 hour with our UI components, SDKs & guides.

Start for free

Share this article


LinkedInTwitterFacebook

Enjoyed this read?

🤝 Join our Passkeys Community

Share passkeys implementation tips and get support to free the world from passwords.

🚀 Subscribe to Substack

Get the latest news, strategies, and insights about passkeys sent straight to your inbox.