In a React Native app, what must the website and the native projects configure so iOS Universal Links and Android App Links verify?
answer
- both sides must name each other
- applinks: entitlement, no https://
- TeamID.bundleID in the AASA
- autoVerify plus VIEW, DEFAULT, BROWSABLE
- package_name and sha256_cert_fingerprints
basics
~10 sThe domain hosts /.well-known/apple-app-site-association (Team ID plus bundle ID, paths) and /.well-known/assetlinks.json (package name, SHA-256 signing fingerprints) over HTTPS; the app declares applinks:domain in Associated Domains and an https intent filter with autoVerify.
solid answer
~40 sVerification is a **two-way association**. On the website, iOS needs `/.well-known/apple-app-site-association`, a JSON file without an extension whose `applinks` section lists the app as `<TeamID>.<bundleID>` and the paths it handles; Android needs `/.well-known/assetlinks.json` with the `delegate_permission/common.handle_all_urls` relation, the `package_name` and the `sha256_cert_fingerprints` of every key that signs the app. Both must be served over HTTPS with a valid certificate and no redirects. In the app, iOS needs the Associated Domains entitlement `com.apple.developer.associated-domains` containing `applinks:recipes.example.com` (host only, no `https://`), and Android needs an intent filter with action `VIEW`, categories `DEFAULT` and `BROWSABLE`, the `https` scheme and host, and `android:autoVerify="true"`. In an Expo project those come from `ios.associatedDomains` and `android.intentFilters` in app config.
code
xml · 8 lines<activity android:name=".MainActivity" android:launchMode="singleTask" android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="recipes.example.com" android:pathPrefix="/r" />
</intent-filter>
</activity>go deeper
Recall the two file names under /.well-known and that the app must list the domain too; both sides have to agree.
Explain what each file identifies (Team ID plus bundle ID versus package plus signing fingerprint), the applinks: entitlement format and the autoVerify intent filter, and how Expo generates them.
Point out the serving rules that break verification in practice: redirects, missing hosts, wrong fingerprints, and that neither OS re-checks the file on every tap.
Decide how many domains and subdomains the app should claim and who owns the verification files, since every claimed host becomes an operational dependency of the mobile release.
## Why two sides An app cannot prove it owns a domain by claiming it, and a website cannot force an app to open. So both platforms ask each side to **name the other**: the app lists the domain, and the domain lists the app. The OS fetches the website's file and compares it with the installed app. Only a match makes the `https` URL open the app. This is the **two-way association**, and every piece below is one half of it. ## The website half Serve both files under `/.well-known/` on **every host** the app claims, so `recipes.example.com` and `www.example.com` each need their own copy. | | iOS | Android | |---|---|---| | Path | `/.well-known/apple-app-site-association` (no `.json` extension) | `/.well-known/assetlinks.json` | | Identifies the app by | `<TeamID>.<bundleID>` in `appID` or `appIDs` | `package_name` plus `sha256_cert_fingerprints` | | Also lists | which paths open the app (`paths`, or `components` with `exclude`) | nothing about paths; paths live in the intent filter | | Serving rules | HTTPS, valid certificate, no redirects, at most 128 KB uncompressed | HTTPS, valid certificate, no redirects, `Content-Type: application/json` | Key details: - The **Team ID** is the Apple developer team that signs the app. A mismatch with the provisioning profile's team is enough to break verification. - The **fingerprints** are SHA-256 hashes of the app's **signing certificate**, not of the website's TLS certificate. List every key that signs a build users install: release, and if you test App Links in development, the debug keystore too. - On iOS the paths are part of the website file, so the website decides which URLs open the app; `/r/*` can open the app while `/blog/*` stays on the web. ## The app half **iOS.** The app must be signed with the Associated Domains capability, and its entitlements must contain `com.apple.developer.associated-domains` with entries such as `applinks:recipes.example.com`. The value is a bare host: writing `https://` inside the entry is a well-known mistake that silently disables Universal Links. In a bare React Native app you add the capability in Xcode; the provisioning profile must include it too. **Android.** `AndroidManifest.xml` needs an intent filter on the activity that hosts React Native, normally `MainActivity`: 1. action `android.intent.action.VIEW`; 2. categories `DEFAULT` and `BROWSABLE`; 3. `<data>` with `android:scheme="https"`, the `android:host` and optionally a path prefix; 4. `android:autoVerify="true"` on the filter, which tells Android to fetch `assetlinks.json` for its hosts and, on success, make the app the verified handler. **Expo.** With Continuous Native Generation the same halves come from app config: `ios.associatedDomains` becomes the entitlement, and an `android.intentFilters` entry with `autoVerify: true` becomes the manifest filter at prebuild. EAS Build also registers the Associated Domains capability with Apple for you. ## Common mistakes that break the association Most broken setups fail on one of a handful of details, and each one fails silently: - **Protocol in the entitlement**: `applinks:https://recipes.example.com` instead of the bare host. - **Wrong identifier in the AASA**: a staging bundle ID, or a Team ID from a different developer team than the one that signs the build. - **Wrong fingerprint in `assetlinks.json`**: the upload key or a local keystore listed, while users install builds signed by a different key. - **A redirect on `/.well-known/`**: for example the apex domain redirecting to `www`, or a login wall in front of the file. - **A missing host**: the intent filter or entitlement names `www.example.com` and `recipes.example.com`, but only one of them serves the files. - **Expecting instant effect**: editing a file on the server does not update devices that already verified at install time. ## When the OS checks Verification is not done on every tap. iOS fetches the association file when the app is installed or updated and refreshes it only occasionally after that; Android verifies when the app is installed or updated. A change to either file therefore does not reach existing installs immediately, which matters when you debug. ## What React Native does afterwards Once verification succeeds, the incoming URL is just a URL. React Native delivers it through `Linking`: `Linking.getInitialURL()` for a cold start and the `'url'` event for a running app. On iOS a bare project's `AppDelegate` forwards the universal-link activity to `RCTLinkingManager`; on Android the URL arrives in the `VIEW` intent. The same JavaScript handles recipe links whether they came from a scheme or a verified domain.
- Why does the iOS file list paths while the Android file does not?On iOS the website decides which paths open the app, through `paths` or `components` in the AASA, including `exclude` rules. On Android, `assetlinks.json` only proves the domain trusts the app; the paths the app accepts are declared in the manifest's intent filter `<data>` elements. So adding a new app path on iOS is a website change, while on Android it is an app change.
- In an Expo project, how do you declare the app half without editing native files?Put `applinks:recipes.example.com` in `ios.associatedDomains` and add an `android.intentFilters` entry with `action: "VIEW"`, `autoVerify: true`, `category: ["BROWSABLE", "DEFAULT"]` and a `data` entry with `scheme: "https"` and the host. Prebuild writes the entitlement and the manifest filter, and EAS Build registers the Associated Domains capability with Apple.
It works like a reference check: the app's entitlement or manifest says 'I work for recipes.example.com', and the OS phones the domain's /.well-known file to confirm. If the domain does not name the app back, the claim is ignored.
saying these in an interview costs you the question
- The entitlement entry should be applinks:https://recipes.example.com
- sha256_cert_fingerprints holds the website's TLS certificate fingerprint
- Only the apex domain needs the file; subdomains inherit it
- The AASA file must be named apple-app-site-association.json
- Redirecting /.well-known to a CDN host is fine because the OS follows redirects
- autoVerify is optional; Android verifies every https filter anyway