Skip to main content

Deep links, verified App Links, and validation

A deep link is externally supplied input that requests navigation. A verified App Link is an HTTPS deep link whose domain has declared a relationship with your signed app.

LinkExampleMain property
custom schememyshop://orders/42easy, but any app can claim the scheme
ordinary web deep linkhttps://shop.example.com/orders/42has web fallback; may open browser/chooser
verified App Linksame HTTPS URL + verificationdomain ownership association enables trusted routing

Manifest declaration​

<activity
android:name=".MainActivity"
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="shop.example.com"
android:pathPrefix="/orders" />
</intent-filter>
</activity>

Host https://shop.example.com/.well-known/assetlinks.json with package name and SHA-256 signing certificate fingerprints. Include production and intended testing certificates deliberately; do not weaken the relationship with unrelated apps.

Parse into a typed request​

sealed interface ExternalDestination {
data class Order(val id: String) : ExternalDestination
data object Unsupported : ExternalDestination
}

fun parseExternalUri(uri: Uri): ExternalDestination {
if (uri.scheme != "https" || uri.host != "shop.example.com") {
return ExternalDestination.Unsupported
}
val segments = uri.pathSegments
if (segments.size != 2 || segments[0] != "orders") {
return ExternalDestination.Unsupported
}
val id = segments[1].takeIf { it.matches(Regex("[A-Za-z0-9-]{1,64}")) }
?: return ExternalDestination.Unsupported
return ExternalDestination.Order(id)
}

Verification proves domain-to-app association; it does not prove the user may access order 42. Fetch through an authenticated repository and enforce authorization on the server.

Attack and failure cases​

  • unexpected scheme/host, encoded slashes, duplicate query parameters, very long values;
  • open redirects (?next=https://evil.example);
  • links to deleted or tenant-inaccessible content;
  • verification file missing, redirected, wrong MIME/JSON, or wrong signing fingerprint;
  • staging links accidentally opening production app;
  • navigation into an exported internal-only screen.
Never deserialize navigation commands

Map a small allowlisted URI grammar to typed destinations. Do not accept arbitrary class names, routes, nested intents, file paths, or SQL-like filter expressions from a URL.

Test the contract​

Use App Links Assistant and device commands to verify association and route handling. Test installed/not installed, signed debug/release builds, logged in/out, malformed URLs, browser fallback, multiple tasks, and warm/cold app states.

Sources​

Exercise​

Define an allowlisted URI grammar for products, orders, and password-reset links. State which may be opened when signed out and which require a one-time server token.