@personql/react-native
v1.0.6
Published
React Native SDK for PersonQL authentication and multi-tenant support
Maintainers
Readme
@personql/react-native
Official PersonQL SDK for React Native applications. Provides authentication, multi-tenant organization management, secure storage, and biometric authentication for iOS and Android.
Features
- 🔐 Secure Authentication - Email/password, OAuth, and biometric authentication
- 🏢 Multi-Tenant Organizations - Complete organization management with RBAC
- 🔒 Secure Storage - Native Keychain (iOS) and Keystore (Android) integration
- 👆 Biometric Auth - Face ID, Touch ID, and Fingerprint support
- 🎨 Pre-built UI - Ready-to-use SignInScreen component
- 📱 Cross-Platform - Works on iOS and Android
- 🪝 React Hooks - Modern hooks-based API
- 💪 TypeScript - Full type safety
Installation
npm install @personql/react-native
# or
yarn add @personql/react-native
# or
pnpm add @personql/react-nativePeer Dependencies
Install required peer dependencies:
npm install react react-native @react-native-async-storage/async-storage react-native-keychain react-native-biometricsiOS Setup
cd ios && pod installAdd to your Info.plist:
<key>NSFaceIDUsageDescription</key>
<string>Authenticate to sign in to PersonQL</string>Android Setup
No additional setup required for basic functionality.
Quick Start
1. Setup Provider
Wrap your app with PersonQLProvider:
import { PersonQLProvider } from '@personql/react-native';
function App() {
return (
<PersonQLProvider apiUrl="https://gateway.personql.com">
<AppContent />
</PersonQLProvider>
);
}2. Use the Pre-built SignInScreen
import { SignInScreen } from '@personql/react-native';
import { useState } from 'react';
function AuthFlow() {
const [isAuthenticated, setIsAuthenticated] = useState(false);
if (!isAuthenticated) {
return (
<SignInScreen
apiUrl="https://gateway.personql.com"
onSignInSuccess={() => setIsAuthenticated(true)}
enableBiometric
/>
);
}
return <Dashboard />;
}3. Or Build Custom Auth UI
import { useAuth } from '@personql/react-native';
import { View, TextInput, Button, Text } from 'react-native';
import { useState } from 'react';
function CustomSignIn() {
const {
signIn,
signInWithBiometric,
loading,
error,
biometricAvailable,
} = useAuth({
apiUrl: 'https://gateway.personql.com',
enableBiometric: true,
});
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSignIn = async () => {
try {
await signIn({ email, password });
// Navigate to app
} catch (error) {
console.error('Sign in failed:', error);
}
};
return (
<View>
<TextInput
placeholder="Email"
value={email}
onChangeText={setEmail}
autoCapitalize="none"
/>
<TextInput
placeholder="Password"
value={password}
onChangeText={setPassword}
secureTextEntry
/>
{error && <Text>{error.message}</Text>}
<Button title="Sign In" onPress={handleSignIn} disabled={loading} />
{biometricAvailable && (
<Button
title="Sign In with Biometric"
onPress={signInWithBiometric}
disabled={loading}
/>
)}
</View>
);
}API Reference
PersonQLProvider
Context provider that initializes PersonQL client.
<PersonQLProvider
apiUrl="https://gateway.personql.com"
clientId="optional-client-id"
>
{children}
</PersonQLProvider>Props:
apiUrl(required): Your PersonQL API endpointclientId(optional): Client identifier for trackingchildren: React components
useAuth Hook
Manages authentication state and operations.
const {
// State
isAuthenticated,
user,
loading,
error,
biometricAvailable,
// Auth methods
signIn,
signUp,
signOut,
signInWithBiometric,
// Token methods
refreshToken,
isTokenExpired,
// Biometric methods
enableBiometricAuth,
disableBiometricAuth,
// Utility
clearError,
} = useAuth({
apiUrl: 'https://gateway.personql.com',
enableBiometric: true,
biometricPrompt: 'Authenticate to continue',
onAuthStateChange: (isAuth) => console.log('Auth changed:', isAuth),
});Configuration:
apiUrl(required): Your PersonQL API endpointenableBiometric(optional): Enable biometric authenticationbiometricPrompt(optional): Custom biometric prompt messageonAuthStateChange(optional): Callback when auth state changes
Methods:
signIn(credentials)
Sign in with email and password.
await signIn({
email: '[email protected]',
password: 'password123',
});signUp(credentials)
Create a new account.
await signUp({
email: '[email protected]',
password: 'password123',
firstName: 'John',
lastName: 'Doe',
});signOut()
Sign out the current user.
await signOut();signInWithBiometric()
Authenticate using biometrics (Face ID, Touch ID, Fingerprint).
await signInWithBiometric();refreshToken()
Manually refresh the access token.
await refreshToken();isTokenExpired()
Check if the current token is expired.
const expired = await isTokenExpired();useOrganization Hook
Manages multi-tenant organizations and permissions.
const {
// State
currentOrganization,
organizations,
loading,
switching,
error,
currentRole,
members,
roles,
// Methods
fetchOrganizations,
switchOrganization,
createOrganization,
updateOrganization,
hasPermission,
loadMembers,
loadRoles,
refreshOrganizations,
clearError,
} = useOrganization();Methods:
fetchOrganizations()
Load all organizations for the current user.
await fetchOrganizations();switchOrganization(organizationId)
Switch to a different organization.
await switchOrganization('org_123');createOrganization(data)
Create a new organization.
const org = await createOrganization({
name: 'My Company',
slug: 'my-company',
});updateOrganization(organizationId, updates)
Update organization details.
const updated = await updateOrganization('org_123', {
name: 'New Name',
primary_color: '#007AFF',
});hasPermission(permission)
Check if current user has a specific permission.
if (hasPermission('users:write')) {
// User can create/edit users
}
if (hasPermission('billing:*')) {
// User has all billing permissions
}Permission Format:
resource:action- e.g.,users:read,billing:writeresource:*- All actions for a resource*- All permissions (superuser)
loadMembers(organizationId?)
Load members of an organization.
await loadMembers(); // Current org
await loadMembers('org_123'); // Specific orgloadRoles(organizationId?)
Load roles for an organization.
await loadRoles(); // Current org
await loadRoles('org_123'); // Specific orgSignInScreen Component
Pre-built sign-in screen with email/password and biometric authentication.
<SignInScreen
apiUrl="https://gateway.personql.com"
onSignInSuccess={() => navigation.navigate('Dashboard')}
onSignUpPress={() => navigation.navigate('SignUp')}
onForgotPasswordPress={() => navigation.navigate('ForgotPassword')}
Logo={MyLogoComponent}
primaryColor="#007AFF"
enableBiometric
biometricPrompt="Sign in to continue"
/>Props:
apiUrl(required): Your PersonQL API endpointonSignInSuccess: Callback when sign in succeedsonSignUpPress: Callback when user taps "Sign Up"onForgotPasswordPress: Callback when user taps "Forgot Password"Logo: Custom logo componentprimaryColor: Theme color for buttons and linksenableBiometric: Enable biometric authenticationbiometricPrompt: Custom biometric prompt message
SecureStorage
Low-level secure storage using native Keychain/Keystore.
import { SecureStorage } from '@personql/react-native';
const storage = new SecureStorage({
service: 'com.myapp', // Optional
});
// Store
await storage.setItem('key', 'value');
// Retrieve
const value = await storage.getItem('key');
// Remove
await storage.removeItem('key');
// Clear all
await storage.clear();
// Check availability
const available = await storage.isAvailable();TokenManager
Manages authentication tokens securely.
import { TokenManager } from '@personql/react-native';
const tokenManager = new TokenManager();
// Save tokens
await tokenManager.saveTokens(
'access_token',
'refresh_token',
Date.now() + 3600000 // Expiry timestamp
);
// Get tokens
const accessToken = await tokenManager.getAccessToken();
const refreshToken = await tokenManager.getRefreshToken();
// Check expiry
const expired = await tokenManager.isTokenExpired();
// Clear tokens
await tokenManager.clearTokens();
// Organization management
await tokenManager.saveCurrentOrganization('org_123');
const orgId = await tokenManager.getCurrentOrganization();
await tokenManager.clearCurrentOrganization();BiometricAuth
Biometric authentication wrapper.
import { BiometricAuth } from '@personql/react-native';
const biometric = new BiometricAuth();
// Check availability
const capabilities = await biometric.checkAvailability();
if (capabilities.available) {
console.log('Biometry type:', capabilities.biometryType);
// 'FaceID', 'TouchID', or 'Biometrics'
}
// Authenticate
const success = await biometric.authenticate('Sign in to continue');
// Get biometry type name
const typeName = await biometric.getBiometryTypeName();
// "Face ID", "Touch ID", or "Biometric Authentication"
// Advanced: Create keys for signing
const keys = await biometric.createKeys();
if (keys) {
console.log('Public key:', keys.publicKey);
}
// Advanced: Create signature
const signature = await biometric.createSignature('data_to_sign');
// Delete keys
await biometric.deleteKeys();Multi-Tenant Examples
Organization Switcher
import { useOrganization } from '@personql/react-native';
import { View, Text, TouchableOpacity, FlatList } from 'react-native';
function OrganizationSwitcher() {
const {
currentOrganization,
organizations,
switchOrganization,
switching,
} = useOrganization();
return (
<FlatList
data={organizations}
keyExtractor={(org) => org.id}
renderItem={({ item }) => (
<TouchableOpacity
onPress={() => switchOrganization(item.id)}
disabled={switching || item.id === currentOrganization?.id}
>
<Text>{item.name}</Text>
{item.id === currentOrganization?.id && <Text>✓ Active</Text>}
</TouchableOpacity>
)}
/>
);
}Permission-Based Rendering
import { useOrganization } from '@personql/react-native';
import { View, Button } from 'react-native';
function UserManagement() {
const { hasPermission } = useOrganization();
return (
<View>
{hasPermission('users:read') && (
<Button title="View Users" onPress={viewUsers} />
)}
{hasPermission('users:write') && (
<Button title="Add User" onPress={addUser} />
)}
{hasPermission('users:delete') && (
<Button title="Delete User" onPress={deleteUser} />
)}
</View>
);
}Create Organization
import { useOrganization } from '@personql/react-native';
import { View, TextInput, Button } from 'react-native';
import { useState } from 'react';
function CreateOrganization() {
const { createOrganization } = useOrganization();
const [name, setName] = useState('');
const [slug, setSlug] = useState('');
const handleCreate = async () => {
try {
const org = await createOrganization({ name, slug });
console.log('Created:', org);
// Navigation or success handling
} catch (error) {
console.error('Failed to create organization:', error);
}
};
return (
<View>
<TextInput
placeholder="Organization Name"
value={name}
onChangeText={setName}
/>
<TextInput
placeholder="Slug (URL-friendly)"
value={slug}
onChangeText={setSlug}
autoCapitalize="none"
/>
<Button title="Create" onPress={handleCreate} />
</View>
);
}Types
User
interface User {
id: string;
email: string;
firstName?: string;
lastName?: string;
avatar?: string;
createdAt: string;
updatedAt: string;
}Organization
interface Organization {
id: string;
name: string;
slug: string;
domain?: string;
logo_url?: string;
primary_color?: string;
plan_type: 'free' | 'starter' | 'pro' | 'enterprise';
max_users: number;
max_api_calls: number;
settings?: Record<string, any>;
status: 'active' | 'suspended' | 'deleted';
created_at: string;
updated_at: string;
}Role
interface Role {
id: string;
organization_id: string;
name: string;
description?: string;
permissions: string[];
is_system: boolean;
created_at: string;
updated_at: string;
}OrganizationMember
interface OrganizationMember {
id: string;
organization_id: string;
user_id: string;
role_id: string;
status: 'pending' | 'active' | 'suspended';
created_at: string;
updated_at: string;
}Security Best Practices
- Never log sensitive data - Avoid logging tokens, passwords, or biometric data
- Use HTTPS - Always use HTTPS endpoints in production
- Token expiry - Tokens expire automatically; use
refreshToken()as needed - Biometric fallback - Always provide password fallback for biometric auth
- Keychain access - Data is encrypted at rest using native APIs
- Permission checking - Always check permissions before showing/allowing actions
Troubleshooting
iOS Biometric Not Working
Make sure you've added NSFaceIDUsageDescription to Info.plist:
<key>NSFaceIDUsageDescription</key>
<string>Authenticate to sign in</string>Android Keystore Issues
If you encounter keystore errors, try clearing app data or reinstalling.
Token Refresh Fails
Ensure your refresh token hasn't expired. Tokens have a limited lifetime.
Organization Not Switching
Make sure the user has access to the organization and it's in active status.
Support
- Documentation: https://docs.personql.com
- GitHub: https://github.com/FinHub-vc/PersonQL
- Issues: https://github.com/FinHub-vc/PersonQL/issues
License
MIT License - see LICENSE file for details
