UNPKG

expo-ii-integration

Version:

Expo library to enable smartphone native applications to invoke Internet Identity through a web application

595 lines (434 loc) 22 kB
# expo-ii-integration This library enables Expo applications (both web and native) to authenticate with Internet Identity through a web application bridge. It provides a seamless integration between Expo applications and Internet Identity authentication, with different authentication flows for web and native platforms. ## Security Implementation Details This library implements the secure integration pattern described in the [Internet Computer documentation](https://internetcomputer.org/docs/building-apps/security/iam#integrating-internet-identity-on-mobile-devices) to address several critical security concerns when integrating Internet Identity with mobile applications. ### Security Overview When integrating Internet Identity with mobile applications, there are several critical security concerns that must be addressed: 1. **Public Key Verification**: The most critical security measure is ensuring that the delegation chain's public key matches your app's public key. This prevents delegation theft and ensures that only your legitimate app can use the delegation chain. 2. **Delegation Theft**: Malicious applications could intercept the delegation chain issued by Internet Identity, potentially allowing an attacker to impersonate the user. 3. **Cross-App Delegation Misuse**: A delegation issued for one application could potentially be misused by another application. 4. **Phishing Attacks and Deep Link Security**: Users might be tricked into authenticating through a malicious application, leading to delegation theft. 5. **Insecure Communication Channels**: Delegation chains could leak through insecure communication channels. 6. **Platform-Specific Security**: Different platforms require different security approaches. expo-ii-integration addresses these concerns through a comprehensive security model that includes: - **Public Key Verification**: Critical security check ensuring the delegation chain's public key matches your app's public key - **Secure Delegation Chain**: Using an intermediate session key that attackers cannot control - **App Links/Universal Links**: Binding domain names to specific mobile apps - **Secure Storage**: Using platform-specific secure storage for keys and delegation chains - **Origin Validation**: Implementing proper origin validation for web messaging ### Security Concerns Addressed #### 1. Public Key Verification (Primary Security Measure) **Problem:** Without proper public key verification, a malicious application could trick your app into using a delegation chain with a mismatching public key. While the IC would reject such a message, the delegation chain would already have leaked to boundary and potentially replica nodes where an attacker could steal it. **Solution:** expo-ii-integration implements strict public key verification: ```typescript // Critical security check: Verify that the delegation chain's public key // matches your app's public key if ( !arrayBufferEquals(delegationChain.publicKey, appKey.getPublicKey().toDer()) ) { throw new Error('Delegation public key does not match app key'); } ``` This verification is crucial because: 1. **Preventing Delegation Leakage**: Without this verification, a malicious application could trick your app into using a delegation chain with a mismatching public key. 2. **Ensuring Key Consistency**: The verification ensures that the public key in the delegation chain matches your app's public key, maintaining the integrity of the authentication process. 3. **Protecting Against Man-in-the-Middle Attacks**: By verifying the delegation chain before using it, the app can detect if the delegation has been tampered with during transmission. #### 2. Protection Against Delegation Theft **Problem:** Without proper safeguards, a malicious application could intercept the delegation chain issued by Internet Identity, potentially allowing an attacker to impersonate the user. **Solution:** expo-ii-integration implements a secure delegation chain approach: - Uses an intermediate session key generated and stored by the web app proxy - Creates a delegation chain with two delegations: 1. From the II canister key to the intermediate key (generated by II) 2. From the intermediate key to the mobile app public key (signed by the proxy) - This approach ensures that the delegation chain is properly secured. By using an intermediate session key that the attacker cannot control, even if the delegation issued by II is intercepted, the attacker cannot use it because they don't have access to the intermediate session private key. This prevents the delegation from being misused even if it falls into the wrong hands. #### 3. Cross-App Delegation Misuse Prevention **Problem:** A malicious application could intercept the postMessage communication between your app and Internet Identity, potentially stealing the delegation chain. **Solution:** expo-ii-integration implements strict origin validation for postMessage communication: ```typescript // In the web app proxy window.addEventListener('message', (event) => { // Critical security check: Verify that the message origin is from your legitimate app if (event.origin !== expectedOrigin) { console.error('Rejected message from unexpected origin:', event.origin); return; } // Process the message only if it's from a trusted origin // ... }); ``` This origin validation is crucial because: 1. **Preventing Message Interception**: Without this check, any website could send messages to your proxy and potentially intercept the delegation chain. #### 4. Protection Against Phishing Attacks and Deep Link Security **Problem:** Users might be tricked into authenticating through a malicious application, leading to delegation theft. **Solution:** expo-ii-integration implements platform-specific security measures: 1. **Platform-Specific Deep Link Security**: - **Android**: Uses App Links to bind your domain to your app, ensuring only your legitimate app can handle the authentication callback - **iOS**: Uses Universal Links to bind your domain to your app, ensuring only your legitimate app can handle the authentication callback When using `easDeepLinkType: "modern"`, the library ensures that the authentication callback can only return to your legitimate app: ```typescript // In [buildDeepLink.ts](src/utils/buildDeepLink.ts) switch (deepLinkType) { case 'modern': if (frontendCanisterURL.startsWith('https://')) { return frontendCanisterURL; } throw new Error(`Frontend URL is not HTTPS: ${frontendCanisterURL}`); // ... } ``` This ensures that: - The deep link is bound to your specific frontend canister URL - Only your legitimate app can receive the authentication callback - The URL must be HTTPS for additional security 2. **Secure Delegation Chain**: - Implements a secure delegation chain with an intermediate session key that the attacker cannot access - Returns delegation chains using URI fragments that are not included in server requests - Prevents leakage through URL parameters or paths 3. **Secure Storage**: - Uses secure storage mechanisms to protect the intermediate session key - Ensures the critical security element is properly protected #### 5. Secure Communication Channels **Problem:** Delegation chains could leak through insecure communication channels. **Solution:** expo-ii-integration: - Uses secure storage mechanisms for both keys and delegation chains - Implements proper validation before using delegation chains in IC messages - Ensures that delegation chains are not exposed in URL parameters or paths #### 6. Platform-Specific Security Considerations **Problem:** Different platforms require different security approaches. **Solution:** expo-ii-integration: - Provides platform-specific implementations for web and native environments - Uses appropriate secure storage mechanisms for each platform - Implements proper origin validation for web messaging - Handles authentication flows differently based on the platform ### Implementation Details The library implements these security measures through: 1. **Secure Key Management:** - `AppKeyStorage` for securely storing the application's Ed25519KeyIdentity - Platform-specific secure storage implementations 2. **Delegation Chain Handling:** - `DelegationStorage` for securely storing and validating delegation chains - Automatic validation of delegation chains before use - Proper cleanup of expired delegations 3. **Authentication Flow Security:** - Platform-specific authentication flows that maintain security - Proper origin validation for web messaging - Secure communication between the proxy and mobile app 4. **Type Safety and Validation:** - Type-safe interfaces to prevent misuse - Runtime validations to ensure security requirements are met - Proper error handling for security-related issues ## EAS Deep Link Type and App Links/Universal Links A critical security feature of expo-ii-integration is its support for App Links (Android) and Universal Links (iOS) through the `easDeepLinkType` parameter. ### Configuration When building your Expo app with EAS Build, you should set the `EXPO_PUBLIC_EAS_DEEP_LINK_TYPE` environment variable to `"modern"`: ```json { "preview": { "distribution": "internal", "env": { "EXPO_PUBLIC_EAS_BUILD_PROFILE": "preview", "EXPO_PUBLIC_EAS_DEEP_LINK_TYPE": "modern" } }, "production": { "autoIncrement": true, "env": { "EXPO_PUBLIC_EAS_BUILD_PROFILE": "production", "EXPO_PUBLIC_EAS_DEEP_LINK_TYPE": "modern" } } } ``` ### Security Benefits Setting `easDeepLinkType` to `"modern"` is crucial for security because: 1. **Domain Binding**: It forces the authentication flow to use App Links (Android) or Universal Links (iOS), which bind your domain to your specific mobile app. 2. **Preventing Delegation Theft**: This binding prevents malicious applications from intercepting the delegation chain, as only your legitimate app can receive the authentication callback. 3. **Phishing Protection**: Even if a user is tricked into authenticating through a malicious app, the delegation cannot be stolen because the authentication callback will only be handled by your legitimate app. ### Implementation Details The library uses this setting in several ways: 1. In [`useIIIntegration.ts`](src/hooks/useIIIntegration.ts), the `easDeepLinkType` parameter is passed to determine the authentication flow. 2. In [`getDeepLinkType.ts`](src/utils/getDeepLinkType.ts), this parameter is used to set the deep link type to `"modern"`. 3. When `"modern"` is set, the proxy web app uses App Links/Universal Links to ensure the authentication callback returns to your legitimate app. 4. This is implemented in the [`buildDeepLink.ts`](src/utils/buildDeepLink.ts) utility, which generates the appropriate deep link based on the type. This approach is a key security measure that prevents delegation theft and ensures that even if the delegation chain is intercepted, it cannot be used by malicious applications. ### Delegation Chain Verification A critical security measure implemented in expo-ii-integration is the verification of the delegation chain before using it in IC messages. This verification focuses specifically on comparing the public key in the delegation chain with your app's public key: ```typescript // In [useIIIntegration.ts](src/hooks/useIIIntegration.ts) const setupIdentityFromDelegation = async (delegation: string) => { console.log('Processing delegation'); const delegationChain = DelegationChain.fromJSON(delegation); await delegationStorage.save(delegationChain); const appKey = await appKeyStorage.retrieve(); // Critical security check: Verify that the delegation chain's public key // matches your app's public key if ( !arrayBufferEquals(delegationChain.publicKey, appKey.getPublicKey().toDer()) ) { throw new Error('Delegation public key does not match app key'); } const id = DelegationIdentity.fromDelegation(appKey, delegationChain); setIdentity(id); console.log('identity set from delegation'); }; ``` This public key comparison is crucial for security because: 1. **Preventing Delegation Leakage**: Without this verification, a malicious application could trick your app into using a delegation chain with a mismatching public key. While the IC would reject such a message, the delegation chain would already have leaked to boundary and potentially replica nodes where an attacker could steal it. 2. **Ensuring Key Consistency**: The verification ensures that the public key in the delegation chain matches your app's public key, maintaining the integrity of the authentication process. 3. **Protecting Against Man-in-the-Middle Attacks**: By verifying the delegation chain before using it, the app can detect if the delegation has been tampered with during transmission. This verification is implemented using the `arrayBufferEquals` utility function, which performs a secure comparison of the public keys to ensure they match exactly. This is a critical security check that prevents delegation theft and ensures that only your legitimate app can use the delegation chain. ## Features - Seamless Internet Identity authentication in Expo apps - Platform-specific authentication flows: - Web (PC/Smartphone): Modal-based authentication using iframe messaging - Native (iOS/Android): Browser-based authentication using Expo WebBrowser - Secure key and delegation chain management - Platform-agnostic secure storage handling - Type-safe React hooks and context - Path tracking utilities for authentication flow: - `pathWhenLogin`: Tracks the path when authentication was initiated - `clearPathWhenLogin()`: Utility to clear the saved path - Error handling and state management ## Installation ```bash npm install expo-ii-integration ``` ### Dependencies This package has the following peer dependencies that you need to install: ```json { "@dfinity/agent": "^0.20.2", "@dfinity/identity": "^0.20.2", "expo-linking": "~7.0", "expo-router": "~4.0", "expo-web-browser": "~14.0", "react": "~18.3" } ``` ## Usage ### Basic Setup 1. Wrap your app with the `IIIntegrationProvider`: ```tsx import { IIIntegrationProvider, useIIIntegration } from 'expo-ii-integration'; import { AppKeyStorage } from 'expo-ii-integration/storage'; import { DelegationStorage } from 'expo-ii-integration/storage'; function App() { const appKeyStorage = new AppKeyStorage(storage); const delegationStorage = new DelegationStorage(storage); const iiIntegration = useIIIntegration({ localIPAddress: '192.168.0.210', dfxNetwork: 'local', easDeepLinkType: 'legacy', deepLink: 'your-deep-link', frontendCanisterId: 'your-frontend-canister-id', iiIntegrationCanisterId: 'YOUR_II_INTEGRATION_CANISTER_ID', platform: 'web', appKeyStorage, delegationStorage, }); return ( <IIIntegrationProvider value={iiIntegration}> {/* Your app components */} </IIIntegrationProvider> ); } ``` ### Using the Authentication Context ```tsx import { useIIIntegrationContext } from 'expo-ii-integration'; function AuthButton() { const { login, logout, isAuthenticated, isReady, identity, pathWhenLogin, clearPathWhenLogin, authError, } = useIIIntegrationContext(); if (!isReady) return null; return ( <Button onPress={isAuthenticated ? logout : login} title={isAuthenticated ? 'Logout' : 'Login with Internet Identity'} /> ); } ``` ### Path Restoration Example ```tsx import React from 'react'; import FontAwesome from '@expo/vector-icons/FontAwesome'; import { Href, Redirect, Tabs, usePathname } from 'expo-router'; import { LogIn } from '@/components/LogIn'; import { LogOut } from '@/components/LogOut'; import { View, ActivityIndicator } from 'react-native'; import { useIIIntegrationContext } from 'expo-ii-integration'; function TabBarIcon(props: { name: React.ComponentProps<typeof FontAwesome>['name']; color: string; }) { return <FontAwesome size={28} style={{ marginBottom: -3 }} {...props} />; } export default function TabLayout() { const { identity, pathWhenLogin, clearPathWhenLogin } = useIIIntegrationContext(); const pathname = usePathname(); if (identity && pathWhenLogin) { clearPathWhenLogin(); if (pathWhenLogin !== pathname) { console.log('redirecting to', pathWhenLogin); // Show loading indicator while redirecting return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }} > <ActivityIndicator size="large" color="#007AFF" /> <Redirect href={pathWhenLogin as Href} /> </View> ); } } return ( <Tabs screenOptions={{ tabBarActiveTintColor: '#007AFF', headerRight: () => (identity ? <LogOut /> : <LogIn />), headerStyle: { height: 110, }, }} > <Tabs.Screen name="index" options={{ title: 'Identity', tabBarIcon: ({ color }) => <TabBarIcon name="user" color={color} />, }} /> <Tabs.Screen name="two" options={{ title: 'Encryption', tabBarIcon: ({ color }) => <TabBarIcon name="lock" color={color} />, }} /> </Tabs> ); } ``` This example shows how to: - Integrate path restoration with tab navigation - Show authentication UI components in the header - Compare the current path with the saved path to avoid unnecessary redirects - Show a loading indicator during redirection - Use the `Redirect` component from expo-router for navigation - Clear the saved path after initiating the redirect ## API Reference ### useIIIntegration Hook The main hook for Internet Identity integration. #### Parameters ```typescript type UseIIAuthParams = { localIPAddress: string; // Local IP address for development dfxNetwork: string; // dfx network (e.g., 'local', 'ic') easDeepLinkType?: string; // EAS deep link type ('legacy' or 'modern') deepLink: string; // Deep link to determine the type frontendCanisterId: string; // Frontend canister ID iiIntegrationCanisterId: string; // II Integration canister ID platform: string; // Platform identifier (e.g., 'ios', 'android', 'web') appKeyStorage: Ed25519KeyIdentityValueStorageWrapper; // Storage for app's key identity delegationStorage: DelegationChainValueStorageWrapper; // Storage for delegation chain }; ``` #### Returns ```typescript interface IIIntegrationContextType { identity: DelegationIdentity | undefined; // Current user identity isReady: boolean; // Authentication state is initialized isAuthenticated: boolean; // User is authenticated login: () => Promise<void>; // Trigger login flow logout: () => Promise<void>; // Clear authentication pathWhenLogin: string | undefined; // Path when login was initiated clearPathWhenLogin: () => void; // Clear the saved path authError: unknown | undefined; // Authentication errors } ``` ### Storage Classes #### Ed25519KeyIdentityValueStorageWrapper A storage wrapper for Ed25519KeyIdentity that handles serialization and deserialization. ```typescript class Ed25519KeyIdentityValueStorageWrapper implements StorageWrapper<Ed25519KeyIdentity> { constructor(storage: Storage, key: string); find(): Promise<Ed25519KeyIdentity | undefined>; retrieve(): Promise<Ed25519KeyIdentity>; save(value: Ed25519KeyIdentity): Promise<void>; remove(): Promise<void>; } ``` #### DelegationChainValueStorageWrapper A storage wrapper for DelegationChain that includes automatic validation. ```typescript class DelegationChainValueStorageWrapper implements StorageWrapper<DelegationChain> { constructor(storage: Storage, key: string); find(): Promise<DelegationChain | undefined>; retrieve(): Promise<DelegationChain>; save(value: DelegationChain): Promise<void>; remove(): Promise<void>; } ``` #### AppKeyStorage A specialized wrapper for storing the application's Ed25519KeyIdentity. ```typescript class AppKeyStorage extends Ed25519KeyIdentityValueStorageWrapper { constructor(storage: Storage); } ``` #### DelegationStorage A specialized wrapper for storing the application's DelegationChain. ```typescript class DelegationStorage extends DelegationChainValueStorageWrapper { constructor(storage: Storage); } ``` ### API Methods #### login() Initiates the authentication flow: - On web: Opens a modal with Internet Identity using iframe messaging - On native: Opens the system browser using Expo WebBrowser - Automatically saves the current path before initiating login - Handles platform-specific authentication flows #### logout() Clears the current authentication state: - Removes the delegation chain from storage - Clears the identity state - Maintains the app key for future authentication #### clearPathWhenLogin() Clears the saved path that was stored during login: - Used after restoring the user to their original location - Prevents unnecessary redirects - Helps maintain a smooth navigation flow ## Platform-Specific Behavior ### Web - Uses modal-based authentication with iframe messaging - Handles authentication flow within the same window - Maintains application state during authentication ### Native (iOS/Android) - Opens authentication in the system browser - Uses URL scheme for authentication callback - Provides utilities to restore application state after authentication ## Security Features - Secure storage of Ed25519 key pairs - Delegation chain validation and automatic cleanup - Origin validation for web messaging - Platform-specific secure storage implementations - Type-safe interfaces and runtime validations ## Contributing Contributions are welcome! Please read our contributing guidelines for details. ## License MIT License