Skip to main content
Version: 4.6.x.x RR (Android, iOS) / 4.7.x.x RR (Flutter) / 5.x.x.x RR (React Native)

iOS App Extensions

On iOS, an application can ship app extensions: Share and Action extensions, notification extensions, widgets, Apple Wallet issuer provisioning extensions and others. Since iOS SDK 4.6.0, an app extension can authenticate with the credentials that the containing app, the app that ships the extension, has already enrolled with the Nevis Mobile Authentication SDK.

This chapter describes how that works and what its boundaries are. For the setup steps, see iOS App Extensions in the developer guide.

Experimental feature

App extension support is experimental. Its API is marked experimental in the API reference and may still change.

While it is experimental, it is also an iOS-only capability. The Android, Flutter and React Native SDKs do not support app extension contexts.

Process isolation​

An app extension is not part of the containing app at runtime. It is a separate process, with its own container, launched and terminated by the system independently of the containing app. There is no communication channel from an extension into the containing app's running process, and the containing app may not be running at all when the extension is invoked.

An extension therefore cannot delegate authentication to the containing app by asking it to perform the operation. It has to operate on the containing app's SDK data itself. The credentials are reached in place, not transferred: nothing is copied into the extension's own container, and nothing is exported.

The bridge that makes this possible is the keychain. Keychain items are stored by the operating system, outside every app container, and are addressed by an access group. When both targets belong to the same Apple Developer Team and declare the same access group, they address the same items.

Containing app and companion roles​

The two processes have asymmetric roles.

  • The containing app owns the credential store. It registers and deregisters credentials, runs data migrations, and performs the integrity check that resets the store after a reinstall.
  • The app extension runs as a companion. It reads the containing app's data and authenticates with it, but it performs none of the housekeeping that would change or destroy what the containing app owns.

The SDK decides which role it is in from the runtime bundle type (whether the running process is an .appex) rather than from a flag the integrator sets. That is safe because the SDK enforces the configuration in both directions at initialization: the app extension configuration is required inside an app extension and rejected outside one, so an extension is always a companion and a containing app never is.

Companion mode is not read-only. The extension is allowed to change local state that belongs to the current user's session, and it does so through the ordinary SDK API. What it does not do is take over the operations that define the store itself.

What an extension can do​

OperationContaining appApp extension
Register and deregister✅❌ Belongs to the containing app
Run data migrations✅❌ Skipped in companion mode
Re-establish the protection of the local store✅❌ Skipped in companion mode
Reset or wipe the keychain✅❌ Skipped in companion mode
Read local data and authenticate✅✅
Delete a locked-out authenticator locally✅✅

Registration and deregistration are not blocked by the API. They belong in the containing app because they create or destroy the credentials the containing app owns, and because a registration started in a short-lived extension process is unlikely to complete. The rows marked skipped in companion mode are different: the SDK actively omits them at initialization, so that a companion can never corrupt or wipe the containing app's store.

Deleting a locked-out authenticator locally is deliberately allowed. It lets an extension clean up an authenticator the user can no longer verify with, so that the user can re-register it from the containing app.

Shared data​

An extension reads the containing app's credentials, registration records and device identifier as keychain items, directly through the shared keychain access group.

Part of that data is protected at rest, and reading it requires more than access to the keychain items themselves. The containing app makes what a companion needs available inside the shared access group as part of its own initialization. How the SDK derives, stores and protects that material is an internal detail and is not part of the public contract, but two properties of the arrangement are, because integrators depend on them:

  • Only the containing app ever publishes it. A companion reads it. It never writes, regenerates or resets it.
  • A companion never creates it. If the containing app has not published anything yet, the extension does not fall back to establishing its own protection. It fails instead, as described in Initialization order.

This is why the containing app has to initialize the SDK once before any extension can, and why nothing has to be migrated for an app that is already in production.

Reinstall behavior​

Deleting the app destroys its local credential store. When the user reinstalls, the SDK detects that the previous installation is gone, resets its local data and requires the user to register again. This is intended, and app extension support does not weaken it.

An extension never triggers that reset. The check belongs to the containing app, and the SDK omits it in companion mode, so an extension can never destroy the store it is reading.

Keychain access groups versus App Groups​

The two mechanisms are frequently confused, and only one of them is involved here.

An app's keychain access groups are, in order: the entries of its keychain-access-groups entitlement, then its App ID (<TeamPrefix>.<bundleId>), then the entries of its App Groups entitlement. The first entry is the default access group, which is where items written without an explicit group land, including all of the SDK's items.

Two consequences follow:

  • No App Groups capability is required for app extension support. Everything the extension reads travels over the keychain. An App Group is a shared file container, which the SDK does not use for this.
  • An App Group can never be the default access group, because the App ID always exists and appears earlier in the list. An App Group identifier is therefore never the right value to configure, even when the containing app declares one for other reasons.

The guide chapter has a resolution table for deriving the value to configure.

What never leaves the containing app​

The mechanism is built so that a companion cannot damage the containing app's store, and so that no key material is duplicated.

  • Secure Enclave keys are never moved or copied. They are non-exportable by construction. The extension asks the Secure Enclave to use a key that the containing app created; the key itself stays where it is.
  • No keychain item is re-homed. The extension addresses the items in the access group they already live in, which is why an app that is already in production needs no migration.
  • The containing app remains the sole publisher. Whatever a companion needs in order to read the protected records is written by the containing app and by nothing else, and the authoritative copy never leaves the containing app's private container.
  • A companion that finds nothing published fails safe. If the extension runs before the containing app has initialized the SDK, the SDK does not write, regenerate or reset anything in the shared store. It reports InitializationError.ContainingAppInitializationRequired swift, objc instead.

Identity towards the backend​

An extension has a bundle identifier of its own, but the SDK does not use it. The keychain namespace, the device identifier and the facet ID are all derived from the containing app's bundle identifier, which the extension supplies in its configuration.

The backend therefore sees one client, not two:

  • One device, so the device management view of the user is unchanged and the same registered authenticators are addressed.
  • One facet ID. The facet list configured in the backend needs only the containing app's ios:bundle-id: entry; adding one for the extension is unnecessary. See Facet ID.
  • One application identity for Application Attestation. The App Attest key belongs to the containing app. An extension can never create one, so it can only assert with the existing key. Apple does not document the behavior of assertions produced inside an app extension, so verify this path before relying on it. See Application attestation in the developer guide.
note

Not everything resolves against the containing app. The User-Agent header the SDK sends and any localized strings the integrator overrides resolve against the running bundle, which inside an extension is the extension's own.

Initialization order​

The containing app must initialize the SDK once before an extension can use it, because that first initialization is what publishes the data a companion needs in order to read the protected records. From then on the order does not matter, and the containing app does not need to be running.

The failure branch is recoverable by launching the containing app once. It is not an error state the extension can resolve on its own, and by design it does not try: proceeding would mean operating on data it cannot unlock.

Security considerations​

The shared keychain access group is the security boundary of the SDK's local data. Every target of the same Apple Developer Team that declares that access group can address the items in it, including everything the containing app publishes for its companions.

  • Keep the group narrow. Declare it only on the containing app and on the extensions that genuinely need to authenticate. Every additional target that declares it widens the boundary.
  • Prefer the group the items already live in. Reusing the containing app's existing default access group means no item is re-homed and no item becomes reachable to a target that could not reach it before.
  • The team prefix is part of the group. Targets signed by a different Apple Developer Team can never address the items, which is what stops another vendor's app from claiming the same group name.

The other protections are unchanged by this feature: keys stay in the Secure Enclave, user verification still runs against the operating system, and the containing app remains the only process that can register, deregister or reset the store. See Security considerations for the full picture.