react-native-icp-auth-kit
v0.1.0
Published
Internet Computer Protocol (ICP) authentication and agent library for React Native
Maintainers
Readme
React Native ICP Auth Kit
Identity management and authentication library for Internet Computer Protocol (ICP) in React Native
Features • Installation • Quick Start • Documentation • Example
Overview
A complete authentication and identity management solution for building ICP wallet applications in React Native. This library provides everything you need for secure key management, wallet recovery, and canister interaction.
Perfect for:
- 🏦 Cryptocurrency wallets
- 🔐 Self-custody applications
- 🌐 Decentralized apps (dApps) with direct key management
- 💼 ICP-based mobile applications
Not for Internet Identity/NFID delegation flows - see ICP-hub/react-native-icp-auth for WebAuthn-based authentication.
Features
✅ Ed25519 Identity Management - Generate and manage cryptographic identities ✅ BIP39 Seed Phrase Recovery - Industry-standard 12/24-word recovery phrases ✅ Secure Storage - iOS Keychain & Android Keystore support ✅ Session Persistence - Automatic wallet restoration on app restart ✅ ICP Agent Integration - Simplified canister query and update calls ✅ TypeScript Support - Full type definitions included ✅ Private Key Export/Import - Flexible backup options ✅ Zero Native Dependencies - Pure JavaScript cryptography
Installation
npm install react-native-icp-auth-kitRequired Peer Dependencies
npm install react-native-get-random-values
npm install @react-native-async-storage/async-storageOptional Dependencies
For secure storage (iOS Keychain/Android Keystore):
npm install expo-secure-storeiOS Setup
cd ios && pod install && cd ..Quick Start
1. Setup Crypto Polyfill
Add this to your app's entry file (e.g., index.js or App.tsx):
import 'react-native-get-random-values'; // Must be first import
import { createAuthClient, generateIdentityWithSeed } from 'react-native-icp-auth-kit';2. Create Your First Wallet
import { createAuthClient, generateIdentityWithSeed } from 'react-native-icp-auth-kit';
async function createWallet() {
// Create auth client
const authClient = await createAuthClient();
// Generate wallet with 12-word seed phrase
const { identity, seedPhrase } = generateIdentityWithSeed();
// Show seed phrase to user - they MUST save it!
console.log('Save this seed phrase:', seedPhrase);
// Login with the identity
await authClient.login(identity);
// Get principal ID
const principal = authClient.getPrincipalText();
console.log('Your Principal:', principal);
}3. Restore Session on App Restart
async function restoreSession() {
const authClient = await createAuthClient();
// Try to restore previous session
const hasSession = await authClient.autoLogin();
if (hasSession) {
console.log('Welcome back!', authClient.getPrincipalText());
} else {
console.log('No saved wallet found');
}
}4. Interact with Canisters
import { createAgentManager } from 'react-native-icp-auth-kit';
import { idlFactory } from './declarations/my-canister';
async function callCanister() {
const authClient = await createAuthClient();
await authClient.autoLogin();
// Create agent
const agent = createAgentManager({
identity: authClient.getIdentity(),
network: 'mainnet',
});
// Query call (read-only)
const balance = await agent.query({
canisterId: 'rrkah-fqaaa-aaaaa-aaaaq-cai',
methodName: 'getBalance',
idlFactory,
});
// Update call (modifies state)
const result = await agent.update({
canisterId: 'rrkah-fqaaa-aaaaa-aaaaq-cai',
methodName: 'transfer',
args: [{ to: 'principal-id', amount: 100 }],
idlFactory,
});
}Documentation
Table of Contents
Wallet Recovery Methods
🔑 Seed Phrase Recovery (Recommended)
Industry standard used by MetaMask, Trust Wallet, and most cryptocurrency wallets.
import { generateIdentityWithSeed, fromSeedPhrase } from 'react-native-icp-auth-kit';
// Create wallet with seed phrase
const { identity, seedPhrase } = generateIdentityWithSeed(128); // 12 words
// Or use 256 for 24 words: generateIdentityWithSeed(256)
// Display to user
console.log('Seed Phrase:', seedPhrase);
// Example: "witch collapse practice feed shame open despair creek road again ice least"
// Later: Recover from seed phrase
const recoveredIdentity = fromSeedPhrase(seedPhrase);
// ✅ Same principal as original!User Flow:
- ✅ User creates wallet → Show 12/24-word seed phrase
- ✅ User writes down seed phrase (paper backup recommended)
- ✅ App stores identity in AsyncStorage
- ❌ User deletes app → All local data lost
- ✅ User reinstalls app → Enters seed phrase → Wallet recovered!
Derivation Path: Uses BIP44 standard m/44'/223'/0'/0/{accountIndex} where 223 is the coin type for Internet Computer.
🔐 Private Key Export/Import
For advanced users who prefer manual key management.
import { exportPrivateKey, importPrivateKey } from 'react-native-icp-auth-kit';
// Export private key
const identity = generateIdentity();
const privateKeyHex = exportPrivateKey(identity);
console.log('Private Key:', privateKeyHex);
// Example: "a7f3b8c2d9e4f1a6b3c8d2e9f4a1b6c3..."
// Import private key
const recoveredIdentity = importPrivateKey(privateKeyHex);⚠️ Warning: Private keys are more complex than seed phrases. Users may find them harder to backup safely.
🛡️ Secure Storage (iOS Keychain/Android Keystore)
Automatic backup that survives app deletion (iOS only with proper configuration).
import {
isSecureStorageAvailable,
saveSecureItem,
getSecureItem,
serializeIdentity,
deserializeIdentity
} from 'react-native-icp-auth-kit';
// Check availability (requires expo-secure-store)
if (isSecureStorageAvailable()) {
// Save to secure storage
const identity = generateIdentity();
const serialized = serializeIdentity(identity);
await saveSecureItem('wallet_identity', serialized);
// Restore from secure storage
const stored = await getSecureItem('wallet_identity');
if (stored) {
const identity = deserializeIdentity(stored);
}
}Platform Support:
- ✅ iOS: Keychain survives app deletion if properly configured
- ⚠️ Android: Keystore data lost on app uninstall
Comparison
| Method | User Experience | Security | Survives Uninstall | Requires Dependency | |--------|----------------|----------|-------------------|---------------------| | Seed Phrase | Manual backup | ⭐⭐⭐⭐ | ✅ (user has backup) | ❌ No | | Private Key | Manual export | ⭐⭐⭐⭐ | ✅ (user has backup) | ❌ No | | Secure Storage | Automatic | ⭐⭐⭐⭐⭐ | ⚠️ iOS only | ✅ expo-secure-store |
💡 Recommendation: Use seed phrase for wallet apps - it's the industry standard and gives users full control.
API Reference
Auth Client API
createAuthClient(config?): Promise<RNAuthClient>
Creates an authenticated client instance.
const authClient = await createAuthClient();Parameters:
config(optional): Configuration objectstorage(optional): Custom storage implementation
Returns: Promise resolving to RNAuthClient instance
authClient.login(identity): Promise<Identity>
Login with an Ed25519 identity and persist to storage.
const { identity } = generateIdentityWithSeed();
await authClient.login(identity);Parameters:
identity:Ed25519KeyIdentityinstance
Returns: Promise resolving to the saved identity
authClient.autoLogin(): Promise<boolean>
Attempts to restore session from storage automatically.
const hasSession = await authClient.autoLogin();
if (hasSession) {
console.log('Session restored!');
}Returns: true if session was restored, false otherwise
authClient.logout(): Promise<void>
Logout and clear all stored identity data.
await authClient.logout();authClient.isAuthenticated(): boolean
Check if user is currently authenticated.
if (authClient.isAuthenticated()) {
console.log('User is logged in');
}Returns: true if authenticated (not anonymous), false otherwise
authClient.getIdentity(): Identity
Get the current identity object.
const identity = authClient.getIdentity();authClient.getPrincipal(): Principal
Get the current principal.
const principal = authClient.getPrincipal();
console.log(principal.toText());authClient.getPrincipalText(): string
Get principal as text string.
const principalText = authClient.getPrincipalText();
// Example: "2vxsx-fae" (anonymous) or "hpikg-6exdt-jn33w-ndty3-fc7jc-tl2lr-buih3-cs3y7-tftkp-sfp62-gqe"Agent Manager API
createAgentManager(config): AgentManager
Creates an agent manager for canister interactions.
const agent = createAgentManager({
identity: authClient.getIdentity(),
network: 'mainnet', // or 'local'
fetchRootKey: false, // true ONLY for local development
});Parameters:
config.identity: Identity to use for signingconfig.network:'mainnet'or'local'config.fetchRootKey(optional): Fetch root key (⚠️ never use in production!)
agentManager.createActor<T>(options): Promise<ActorSubclass<T>>
Create an actor for multiple canister calls.
import { idlFactory } from './declarations/my-canister';
const actor = await agent.createActor({
canisterId: 'rrkah-fqaaa-aaaaa-aaaaq-cai',
idlFactory,
});
const result = await actor.myMethod();agentManager.query<T>(options): Promise<T>
Perform a query call (read-only, fast, no consensus).
const balance = await agent.query({
canisterId: 'rrkah-fqaaa-aaaaa-aaaaq-cai',
methodName: 'getBalance',
args: [{ account: authClient.getPrincipalText() }],
idlFactory,
});agentManager.update<T>(options): Promise<T>
Perform an update call (modifies state, requires consensus, costs cycles).
const result = await agent.update({
canisterId: 'rrkah-fqaaa-aaaaa-aaaaq-cai',
methodName: 'transfer',
args: [{ to: 'recipient-principal', amount: 100 }],
idlFactory,
});agentManager.setIdentity(identity): Promise<void>
Update the agent's identity (useful after login/logout).
await agent.setIdentity(newIdentity);Identity Utilities API
generateIdentity(): Ed25519KeyIdentity
Generate a random Ed25519 identity (not recoverable).
const identity = generateIdentity();
const principal = identity.getPrincipal().toText();⚠️ Warning: This identity cannot be recovered if lost. Use generateIdentityWithSeed() for wallet apps.
generateIdentityWithSeed(strength?): { identity, seedPhrase }
Generate a recoverable identity with BIP39 seed phrase.
// 12 words (128 bits)
const { identity, seedPhrase } = generateIdentityWithSeed();
// 24 words (256 bits)
const { identity, seedPhrase } = generateIdentityWithSeed(256);Parameters:
strength(optional):128for 12 words,256for 24 words (default: 128)
Returns: Object with:
identity:Ed25519KeyIdentityinstanceseedPhrase: BIP39 mnemonic string
fromSeedPhrase(mnemonic, accountIndex?): Ed25519KeyIdentity
Recover identity from BIP39 seed phrase.
const identity = fromSeedPhrase('witch collapse practice feed...');
// Derive different account from same seed
const account1 = fromSeedPhrase('witch collapse...', 1);Parameters:
mnemonic: BIP39 seed phrase stringaccountIndex(optional): Account derivation index (default: 0)
Returns: Ed25519KeyIdentity instance
validateSeedPhrase(mnemonic): boolean
Validate a BIP39 seed phrase.
if (validateSeedPhrase(userInput)) {
const identity = fromSeedPhrase(userInput);
}exportPrivateKey(identity): string
Export private key as hex string.
const privateKey = exportPrivateKey(identity);
// ⚠️ Keep this secure!importPrivateKey(privateKeyHex): Ed25519KeyIdentity
Import identity from private key hex string.
const identity = importPrivateKey(privateKeyHex);serializeIdentity(identity): SerializedIdentity
Serialize identity for storage.
const serialized = serializeIdentity(identity);
await saveSecureItem('wallet', serialized);deserializeIdentity(data): Identity
Deserialize identity from storage.
const data = await getSecureItem('wallet');
const identity = deserializeIdentity(data);Secure Storage API
isSecureStorageAvailable(): boolean
Check if expo-secure-store is available.
if (isSecureStorageAvailable()) {
await saveSecureItem('key', 'value');
}saveSecureItem(key, value): Promise<void>
Save to iOS Keychain / Android Keystore (or fallback to AsyncStorage).
await saveSecureItem('wallet_data', { principal: 'xyz...' });getSecureItem<T>(key): Promise<T | null>
Get from secure storage.
const data = await getSecureItem('wallet_data');removeSecureItem(key): Promise<void>
Remove from secure storage.
await removeSecureItem('wallet_data');Complete Examples
Basic Wallet App
import React, { useEffect, useState } from 'react';
import { View, Button, Text, Alert } from 'react-native';
import {
createAuthClient,
generateIdentityWithSeed,
fromSeedPhrase,
validateSeedPhrase
} from 'react-native-icp-auth-kit';
export default function WalletApp() {
const [authClient, setAuthClient] = useState(null);
const [principal, setPrincipal] = useState('');
const [seedPhrase, setSeedPhrase] = useState('');
useEffect(() => {
initWallet();
}, []);
async function initWallet() {
const client = await createAuthClient();
setAuthClient(client);
// Try to restore existing wallet
const hasWallet = await client.autoLogin();
if (hasWallet) {
setPrincipal(client.getPrincipalText());
}
}
async function createNewWallet() {
// Generate wallet with 24-word seed phrase
const { identity, seedPhrase: mnemonic } = generateIdentityWithSeed(256);
await authClient.login(identity);
setPrincipal(authClient.getPrincipalText());
setSeedPhrase(mnemonic);
// Show seed phrase to user
Alert.alert(
'Save Your Seed Phrase',
`Write this down:\n\n${mnemonic}\n\nYou'll need it to recover your wallet.`,
[{ text: 'I Saved It' }]
);
}
async function recoverWallet() {
Alert.prompt(
'Recover Wallet',
'Enter your seed phrase:',
async (input) => {
if (!validateSeedPhrase(input.trim())) {
Alert.alert('Invalid seed phrase');
return;
}
const identity = fromSeedPhrase(input.trim());
await authClient.login(identity);
setPrincipal(authClient.getPrincipalText());
}
);
}
async function deleteWallet() {
await authClient.logout();
setPrincipal('');
setSeedPhrase('');
}
return (
<View style={{ padding: 20 }}>
{principal ? (
<>
<Text>Principal: {principal}</Text>
{seedPhrase && <Text>Seed: {seedPhrase}</Text>}
<Button title="Delete Wallet" onPress={deleteWallet} color="red" />
</>
) : (
<>
<Button title="Create New Wallet" onPress={createNewWallet} />
<Button title="Recover Wallet" onPress={recoverWallet} />
</>
)}
</View>
);
}Canister Interaction Example
import { createAuthClient, createAgentManager } from 'react-native-icp-auth-kit';
import { idlFactory as ledgerIDL } from './declarations/ledger';
async function checkICPBalance() {
// Initialize auth
const authClient = await createAuthClient();
await authClient.autoLogin();
// Create agent
const agent = createAgentManager({
identity: authClient.getIdentity(),
network: 'mainnet',
});
// Query ICP Ledger
const balance = await agent.query({
canisterId: 'ryjl3-tyaaa-aaaaa-aaaba-cai', // ICP Ledger
methodName: 'account_balance',
args: [{
account: authClient.getIdentity().getPrincipal().toUint8Array()
}],
idlFactory: ledgerIDL,
});
console.log('ICP Balance:', balance.e8s / 100_000_000);
}Local Development with dfx
import { createAgentManager } from 'react-native-icp-auth-kit';
// Start dfx locally: dfx start --clean --background
// Deploy canister: dfx deploy
const agent = createAgentManager({
identity: authClient.getIdentity(),
network: 'local', // http://localhost:4943
fetchRootKey: true, // ⚠️ Required for local development ONLY
});
const result = await agent.query({
canisterId: 'rrkah-fqaaa-aaaaa-aaaaq-cai',
methodName: 'greet',
args: ['Alice'],
idlFactory,
});⚠️ Never use fetchRootKey: true in production! It disables certificate verification.
Example App
A complete working example is available in the example/ directory.
# Clone the repository
git clone https://github.com/yourusername/react-native-icp-auth-kit.git
cd react-native-icp-auth-kit
# Install library dependencies
npm install
# Build the library
npm run build
# Run the example app
npm run exampleThe example demonstrates:
- ✅ Creating wallets with 24-word seed phrases
- ✅ Recovering wallets from seed phrases
- ✅ Auto-restoring wallets on app restart
- ✅ Creating ICP agents for canister interaction
- ✅ Seed phrase visibility toggle with copy functionality
- ✅ Wallet deletion with confirmation
Or run the example directly:
cd example
npm install
npm startThen press i for iOS, a for Android, or w for web.
Architecture
Core Components
react-native-icp-auth-kit/
├── src/
│ ├── core/
│ │ ├── identity.ts # Ed25519 key generation, BIP39 seed phrases
│ │ ├── authClient.ts # Session management, persistence
│ │ └── agent.ts # HttpAgent wrapper, canister calls
│ ├── storage/
│ │ ├── asyncStorage.ts # AsyncStorage wrapper
│ │ └── secureStorage.ts # Secure storage (Keychain/Keystore)
│ └── index.ts # Public API exports
├── dist/ # Compiled JavaScript (npm package)
└── example/ # Example React Native app1. Identity Management (identity.ts)
- Ed25519 Key Generation: Uses
@noble/curvesfor pure JavaScript crypto - BIP39 Seed Phrases: Implements BIP39 standard with English wordlist
- Derivation Path:
m/44'/223'/0'/0/{accountIndex}(BIP44 for ICP) - Identity Types:
anonymous(2vxsx-fae) anded25519 - Serialization: Converts keys to/from hex strings for storage
2. Auth Client (authClient.ts)
- Session Management: Maintains current identity state
- Auto-login: Restores identity from storage on app start
- Storage Key:
icp_auth_clientin AsyncStorage - Anonymous State: Uses principal
2vxsx-faewhen logged out
3. Agent Manager (agent.ts)
- HttpAgent Wrapper: Simplifies
@dfinity/agentAPI - Network Configs:
- Mainnet:
https://ic0.app - Local:
http://localhost:4943
- Mainnet:
- Query vs Update:
- Query: Read-only, fast, no consensus
- Update: Modifies state, slower, requires consensus
4. Storage Layers
AsyncStorage:
- Persists across app restarts
- Lost on app deletion
- All values JSON serialized
Secure Storage:
- iOS Keychain (can survive app deletion)
- Android Keystore (lost on app deletion)
- Requires
expo-secure-store
Security Best Practices
✅ DO
- Always show seed phrases to users and require them to back it up
- Use 24-word seed phrases for high-value wallets (256-bit entropy)
- Validate user input before recovering from seed phrases
- Clear sensitive data from memory after use
- Use secure storage for production wallet apps
- Test recovery flows thoroughly before release
❌ DON'T
- Never store seed phrases in plain text in your database
- Never send seed phrases over the network
- Never use
fetchRootKey: truein production - Never skip seed phrase backup in wallet apps
- Never auto-generate wallets without explicit user action
- Never log sensitive data (private keys, seed phrases) in production
🔒 Recommended Practices
For Wallet Apps:
- Use
generateIdentityWithSeed(256)for 24-word seed phrases - Show seed phrase once during wallet creation
- Require user confirmation that they saved it
- Implement seed phrase recovery flow
- Consider adding biometric authentication before showing keys
- Use secure storage for identity persistence
For dApps:
- Use
generateIdentity()for temporary sessions - Save identity to AsyncStorage for session persistence
- Allow users to export/import identities
- Clear sessions on logout
Troubleshooting
Error: "crypto.getRandomValues() not supported"
Solution: Import polyfill at the top of your entry file:
import 'react-native-get-random-values'; // Must be first importError: "Unable to resolve module @react-native-async-storage/async-storage"
Solution: Install the peer dependency:
npm install @react-native-async-storage/async-storage
cd ios && pod install && cd ..Error: "Network request failed" on iOS
Solution: Add NSAllowsArbitraryLoads to Info.plist for local development:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>⚠️ Only use this in development! For production, use proper SSL certificates.
Seed phrase recovery returns different principal
Cause: Mismatch in account index or derivation path.
Solution: Ensure you're using the same accountIndex (default: 0):
// Creation
const { identity } = generateIdentityWithSeed();
// Recovery - must use same account index
const recovered = fromSeedPhrase(seedPhrase, 0); // default is 0Performance
- Identity Generation: ~50ms (Ed25519 key generation)
- Seed Phrase Generation: ~100ms (BIP39 entropy + key derivation)
- Query Calls: ~200-500ms (network latency)
- Update Calls: ~2-5s (consensus required)
- Session Restore: ~10ms (AsyncStorage read + deserialization)
All cryptographic operations run on the JavaScript thread (no native dependencies).
Dependencies
Core ICP Libraries
@dfinity/agent- HttpAgent and Actor@dfinity/identity- Ed25519KeyIdentity@dfinity/principal- Principal type@dfinity/candid- IDL for canister interfaces
Cryptography
@noble/curves- Ed25519 key generation (pure JS)@scure/bip39- BIP39 mnemonic generation@scure/bip32- HD key derivation (BIP32/BIP44)react-native-get-random-values- Crypto polyfill (required)
React Native (Peer Dependencies)
react>= 16.0.0react-native>= 0.60.0
Optional
@react-native-async-storage/async-storage- Persistent storageexpo-secure-store- Secure storage (iOS Keychain/Android Keystore)
Versioning
This project follows Semantic Versioning:
- Major: Breaking changes
- Minor: New features, backwards compatible
- Patch: Bug fixes, backwards compatible
Contributing
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
# Clone the repo
git clone https://github.com/yourusername/react-native-icp-auth-kit.git
cd react-native-icp-auth-kit
# Install dependencies
npm install
# Build the library
npm run build
# Run tests
npm test
# Watch mode for development
npm run watchLicense
MIT License - see LICENSE file for details
Support
- GitHub Issues: Report bugs or request features
- ICP Forum: Discuss on the Internet Computer Forum
- Documentation: Internet Computer Docs
Acknowledgments
- Built on top of the excellent
@dfinitylibraries - Cryptography powered by
@nobleand@scure - Inspired by ICP-hub/react-native-icp-auth
Built with ❤️ for the Internet Computer ecosystem
