ADR 0005 — iOS WebAuthn: Reuse raxx.app as the RP ID (scope: web + iOS only)
Status: Accepted (amended 2026-04-22: scope narrowed to web + iOS only; console is NOT in scope and uses its own RP ID — see ADR 0012) Date: 2026-04-22 Deciders: software-architect Scope: WebAuthn relying-party configuration for the Raxx iOS app
Context
WebAuthn credentials are scoped to a Relying Party ID (RP ID). For a native iOS app, Apple's AuthenticationServices framework maps an app to an RP ID via an Associated Domains entitlement (webcredentials:<rp-id>) and a hosted apple-app-site-association (AASA) file at https://<rp-id>/.well-known/apple-app-site-association.
We must decide: should the iOS app share the web RP ID (raxx.app) or use its own (e.g. ios.raxx.app)?
This decision is load-bearing and effectively immutable: once users register passkeys under an RP ID, those passkeys cannot be migrated to a different RP ID without forcing every user to re-register.
The existing web auth design (auth.md) previously flagged this as open question #2. Resolved 2026-04-22 by user: raxx.app is the production RP ID. This is immutable once users start registering passkeys — the web, iOS, and console all bind to it.
Decision
Reuse raxx.app as the RP ID for the iOS app. This applies to web + iOS only. Configure an AASA file at https://raxx.app/.well-known/apple-app-site-association listing the iOS app's bundle ID under the webcredentials service. Add the webcredentials:raxx.app entitlement to the Xcode project.
This makes passkeys created on the web natively usable on iOS (iCloud Keychain syncs them), and vice versa. The user signs in once on any device and has access everywhere without re-registering.
Important scope narrowing (2026-04-22): The operator admin console (console.raxx.app) is not in scope for this ADR. Console passkeys live under a separate RP ID — see ADR 0012 for the rationale. Web + iOS share raxx.app; console is isolated.
Consequences
Positive
- Passkeys registered on
raxx.app(web) are immediately usable in the iOS app. Zero friction for existing users. - iCloud Keychain sync means a user who loses their iPhone but has another Apple device recovers access without a separate iOS passkey re-registration.
- One RP ID in Raptor's config — simpler validation logic, single
WEBAUTHN_RP_IDenv var. - Aligns with Apple's recommended pattern for cross-platform passkey sharing (WWDC 2022 session 10092).
Negative / risks
- The AASA file must be served correctly (correct Content-Type, no redirect, reachable by Apple's CDN) before the iOS app passes App Store review. A misconfigured AASA silently breaks passkey assertion on iOS. This must be validated in staging before any TestFlight build.
- The bundle ID in the AASA must match the App Store Connect app exactly. Any bundle ID change later requires an AASA update and re-review.
- If
raxx.appever moves to a different domain, all passkeys become invalid. Domain changes are already a hard operational constraint under the web auth design.
Neutral
- Raptor needs one new static file served at
/.well-known/apple-app-site-association. This is a small addition to the existing Heroku deployment, not a new service. - The
WEBAUTHN_RP_IDandWEBAUTHN_ORIGINenv vars already exist; the iOS app's origin ishttps://raxx.appfor assertion purposes (the native app presents as the associated domain).
Alternatives considered
Separate RP ID for iOS (ios.raxx.app)
Rejected. Forces users to register a separate passkey for the iOS app even if they already have one from the web. Doubles the credential management surface. No security benefit — the iOS AuthenticationServices framework already binds the passkey to the app via the AASA trust, so a separate RP ID adds process complexity without adding trust separation.
Use an app-specific RP ID that the web also trusts
Rejected. Web browsers do not honor app-specific RP IDs via cross-origin association. The only clean shared-RP approach is the one above.
Compliance checklist
- [x] No new credential storage — same public-key store, same
webauthn_credentialstable. - [x] AASA file does not expose PII.
- [x] RP ID change after user registration is blocked by design (would require a forced re-registration, not a config change).
Revisit when
raxx.appdomain changes (hard stop — requires user migration plan, new ADR).- Android app is added (RP ID is shared there too via Digital Asset Links, separate setup but same RP ID decision).
- Raxx adds a separate
app.raxx.appsubdomain as the web origin (would need RP ID re-evaluation; the RP ID can be the parent domain even if the origin is a subdomain, which is fine — confirm this with the actual domain configuration).