Building a Command-Line Tool to Verify iOS AASAFiles

# Building a Command-Line Tool to Verify iOS AASA Files

If you’ve ever set up Universal Links for an iOS application, you know that the Apple App Site Association (AASA) file can sometimes be a headache. Just a single typo, a missing bracket, or a misconfigured HTTP header can break deep linking entirely.

To make this process a bit smoother, I recently built a simple, dependency-free Python tool that verifies AASA files right from the command line.

## What is an AASA File?

The Apple App Site Association (AASA) file is a JSON file that lives on your web server. It establishes a secure connection between your domain and your iOS app, proving to Apple that you own both. When a user clicks a Universal Link (like https://yourdomain.com/path), iOS checks this file to see if the link should open in your app instead of Safari.

For this to work, the AASA file must:
1. Be served over HTTPS with a valid certificate.
2. Be exposed exactly at /.well-known/apple-app-site-association or /apple-app-site-association.
3. Contain valid JSON syntax defining the applinks (and optionally webcredentials or appclips).

If everything is correct, Apple’s CDN will also eventually cache a copy at https://app-site-association.cdn-apple.com/a/v1/[yourdomain.com].

## The Problem: It’s Hard to See Why It Fails

Usually, when deep links stop working, you’re left guessing. Is the file missing? Did the server return the wrong caching headers? Did my JSON syntax break when I updated it?

There are some web-based validators out there, but I wanted something I could run quickly in terminal, integrate into CI/CD pipelines, or just use locally without relying on third-party sites.

## Building verify_aasa.py

I decided to build a simple Python script called verify_aasa.py. The goal was to check the common endpoints where an AASA file might live, fetch it, parse the JSON, and validate its structural integrity.

### 1. Fetching the File

The script checks three distinct locations:

python
paths = [
f"https://{domain}/.well-known/apple-app-site-association",
f"https://{domain}/apple-app-site-association",
f"https://app-site-association.cdn-apple.com/a/v1/{domain}"
]

It iterates through these paths using Python’s built-in urllib.request. If it succeeds in pulling down the valid file, it confirms the HTTP status and notes the Content-Type returned by the server.

*Note: One common hangup is that local Python environments on macOS sometimes struggle with root SSL certificates. To make the tool robust, I added a quick fallback behavior: if the certifi module is available, it uses that for validation; otherwise, it bypasses SSL validation and prints a warning so the user isn’t stuck behind a local configuration error.*

### 2. Validating the JSON

Once it has the file, the next step is parsing it. If the AASA file has trailing commas or weird quotes, json.loads() will throw an error immediately, simulating the confusion iOS might experience.

Assuming it passes the JSON decode, the script validates the structure:

python
if 'applinks' in data:
applinks = data['applinks']
if 'details' in applinks:
for i, app in enumerate(applinks['details']):
appID = app.get('appID')
appIDs = app.get('appIDs')
# Check for modern 'components' or legacy 'paths'
# ...

A common issue nowadays is transitioning from the legacy paths routing to the modern components routing, as well as the transition from appID (string) to appIDs (array). The script validates that at least one of these is correctly defined for each block.

It also supports checking for the existence of webcredentials (Shared Web Credentials) and appclips (App Clips configurations).

## Running the Tool

Running the tool is simple:

bash
python3 verify_aasa.py apple.com

Which produces an output like this:


Verifying AASA file for domain: apple.com

Checking https://apple.com/.well-known/apple-app-site-association ...
Found AASA file at https://apple.com/.well-known/apple-app-site-association
Content-Type: None

Successfully parsed as JSON.

--- Validating AASA Structure ---
Found 'applinks' key.
Found 'applinks.details' key.

App 1: appIDs = ['W74U47NE8E.com.apple.store.Jolly']
Uses modern 'components' routing.

Found 'webcredentials' key.
Includes 1 app(s) for shared web credentials.

Found 'appclips' key.
Includes 1 app(s) for App Clips.

Summary: AASA file syntax looks good!

## Wrapping Up

Building this little tool was a great reminder of how useful small, purpose-built CLI utilities can be. No dependencies (unless you want certifi for strict SSL), no web interfaces, just a quick sanity check to ensure iOS will swallow your AASA file correctly.

You can grab the script and use it in your own projects whenever Universal Links start acting up!
https://gist.github.com/darcyliu/0f15d22751829f2fa4f5c811e15b67c8

-EOF-