Skip to content

Share extension doesn't copy keychain-access-groups entitlement from main app #90

Description

@Nils-Fischer

Problem

I want to use expo-secure-store on the main app, as well as the share app.

However, expo-share-extension plugin currently only copies com.apple.security.application-groups from the main app's entitlements to the share extension, but doesn't copy keychain-access-groups. This prevents the share extension from accessing keychain items (e.g., via expo-secure-store) that are shared with the main app.

Solution

I would suggest changing plugin/src/withShareExtensionEntitlements.ts to copy the keychain-access-groups entitlement as well (if present):

const existingAppGroup = config.ios?.entitlements?.["com.apple.security.application-groups"];
const existingKeychainGroups = config.ios?.entitlements?.["keychain-access-groups"]; // Add this

let shareExtensionEntitlements: Record<string, string | string[]> = {
  "com.apple.security.application-groups": existingAppGroup ?? [getAppGroup(bundleIdentifier)],
};

// Add this block
if (existingKeychainGroups) {
  shareExtensionEntitlements["keychain-access-groups"] = existingKeychainGroups;
}

if (config.ios?.usesAppleSignIn) {
  // ... existing code
}

I know there is some other way to do this with react-native-shared-group-preferences, however that storage solution is not as secure as expo-secure-store and this fix is very minimal.

Activity

  1. SeanBarker182 commented on Nov 7, 2025

    @SeanBarker182
    Contributor

    Hey @Nils-Fischer! I think you should be able to use the app group for this:

    let result = await SecureStore.getItemAsync(key, { accessGroup: "group.com.example.app" });

    From Apple's docs on Keychain Groups:

    When you collect related apps in an application group using the App Groups Entitlement, they share access to a group container, and gain the ability to message each other in certain ways. You can use app group names as keychain access group names, without adding them to the Keychain access groups entitlement.

  2. justYu2001 commented on Nov 15, 2025

    @justYu2001

    @SeanBarker182 Thanks for your answer! It works.

  3. catseyesolutions commented on Nov 17, 2025

    @catseyesolutions

    @justYu2001

    Can you show me an example how you did this? I am also struggling with it

  4. justYu2001 commented on Nov 17, 2025

    @justYu2001

    @catseyesolutions Sure. Here is my code.

    const getEncryptionKey = async () => {
      try {
        let key = await SecureStore.getItemAsync(ENCRYPTION_KEY_STORAGE_KEY, {
          accessGroup: APP_GROUP,
        });
    
        if (!key) {
          key = Crypto.randomUUID();
          await SecureStore.setItemAsync(ENCRYPTION_KEY_STORAGE_KEY, key, {
            accessGroup: APP_GROUP,
          });
        }
    
        return key;
      } catch (error) {
        logError(error);
        throw error;
      }
    };
    
  5. yaliashkevich commented on Mar 22, 2026

    @yaliashkevich

    It would be nice to be able to configure custom entitlements for share extension.

    Good case if app and extension use firebase auth.
    Now you are forced to use app group and useUserAccessGroup which is async. You should make a switch to designated access group as soon as possible in the app and it share extension. Having the switch async makes it inconvenient - you have to wrap root components in both.

    More over you may have other package/library dependency that has no option to select the access groups - and always using default one.

    But you actually can configure default access group by entitlements

    The system considers the first item in the list of access groups to be the app’s default access group. This is the access group that keychain services assumes if you don’t otherwise specify one when adding keychain items. An app group can’t ever be the default, because the app ID is always present and appears earlier in the list. However, a keychain access group can be the default, because it appears before the app ID. In particular, the first keychain access group, if any, that you specify in the corresponding capability becomes the app’s default access group. If you don’t specify any keychain access groups, then the app ID is the default.

    You actually do support custom entitlements in EAS pipeline

    entitlements: {
    ...shareExtensionConfig?.entitlements,
    "com.apple.security.application-groups": [
    getAppGroup(config),
    ],
    ...(config.ios.usesAppleSignIn && {
    "com.apple.developer.applesignin": ["Default"],
    }),
    },

    I would suggest deduplicate logic in withShareExtensionEntitlements (if not intended to be used standalone) by reading entitlements from config.extra.eas.build.experimental.ios.appExtensions made by withExpoConfig

    export const withShareExtensionEntitlements: ConfigPlugin = (config) => {
      return withEntitlementsPlist(config, (config) => {
        const extensionName = getShareExtensionName(config);
    
        const iosExtensions: iOSExtensionConfig[] =
          config.extra?.eas?.build?.experimental?.ios?.appExtensions;
    
        const shareExtensionConfig = iosExtensions?.find(
          (extension) => extension.targetName === extensionName
        );
    
        const targetName = getShareExtensionName(config);
    
        const targetPath = path.join(
          config.modRequest.platformProjectRoot,
          targetName
        );
        const filePath = path.join(targetPath, `${targetName}.entitlements`);
    
        const shareExtensionEntitlements : Record<string, string | string[]> = shareExtensionConfig?.entitlements ?? {};
    
        fs.mkdirSync(path.dirname(filePath), { recursive: true });
        fs.writeFileSync(filePath, plist.build(shareExtensionEntitlements));
    
        return config;
      });
    };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions