@zewstid/react-native
Official ZewstID SDK for React Native - Complete mobile authentication with OAuth, biometric auth, passkeys, magic links, OTP, and more. Build your own standalone authenticator app!
⨠Features
Authentication Methods
- š OAuth/OIDC - Google, GitHub, Microsoft, Apple, Facebook
- š Push Authentication - Okta Verify-style push notifications (v0.2.0+)
- š Biometric Auth - Face ID, Touch ID, Fingerprint
- ⨠Magic Links - Passwordless authentication via email
- š± OTP - SMS and email one-time passwords
- š§ Email/Password - Traditional username/password authentication
- š WebAuthn/Passkeys - Modern biometric authentication
- š”ļø Multi-Factor Auth (MFA) - TOTP authenticator app support
Standalone Authenticator App (v0.3.0+)
- š· QR Code Enrollment - Scan QR codes to add accounts
- ā±ļø TOTP Generation - Local code generation with countdown timer
- š Account Management - List, add, remove authenticator accounts
- š Secure Storage - Encrypted secret storage in KeyChain/KeyStore
- šØ Pre-built Components - with progress indicator
<TOTPCode />
Mobile-Specific Features
- š² Deep Linking - Automatic OAuth callback handling
- š Secure Storage - KeyChain (iOS) and KeyStore (Android)
- š Auto-Refresh - Automatic token refresh
- š± Cross-Platform - iOS ā„13.0 and Android ā„6.0
- ā” React Hooks - Easy integration with React components
- šÆ TypeScript - Full type safety
- šØ Native UIs - Platform-specific biometric prompts
š Installation
npm install @zewstid/react-native --registry https://npm.zewstid.com/
Peer Dependencies
Install required peer dependencies:
npm install react-native-keychain react-native-biometrics \ @react-native-async-storage/async-storage \ @react-native-community/netinfo \ expo-crypto expo-linking
iOS Setup
Add Face ID usage description to
Info.plist<key>NSFaceIDUsageDescription</key> <string>Authenticate with Face ID to access your account securely</string>
Android Setup
Add biometric permissions to
AndroidManifest.xml<uses-permission android:name="android.permission.USE_BIOMETRIC"/> <uses-permission android:name="android.permission.USE_FINGERPRINT"/> <uses-permission android:name="android.permission.INTERNET"/>
Deep Linking Setup
iOS (Info.plist)
Info.plist<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>myapp</string> </array> </dict> </array>
Android (AndroidManifest.xml)
AndroidManifest.xml<activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="myapp" /> </intent-filter> </activity>
š Quick Start
1. Get Your Credentials
Sign up at developers.zewstid.com and create a new application to get your:
- Client ID
- Client Secret (optional for public clients)
2. Wrap Your App
import { ZewstIDProvider } from '@zewstid/react-native'; function App() { return ( <ZewstIDProvider config={{ apiUrl: 'https://api.zewstid.com', clientId: 'your-client-id', redirectUri: 'myapp://auth/callback', }} > <YourApp /> </ZewstIDProvider> ); }
3. Use Authentication
import { useZewstID } from '@zewstid/react-native'; import { View, Button, Text } from 'react-native'; function LoginScreen() { const { user, isAuthenticated, signInWithOAuth, signInWithBiometric, sendMagicLink, signOut, } = useZewstID(); if (isAuthenticated && user) { return ( <View> <Text>Welcome {user.name}!</Text> <Button title="Sign Out" onPress={signOut} /> </View> ); } return ( <View> {/* OAuth / Social Login */} <Button title="Sign in with Google" onPress={() => signInWithOAuth({ provider: 'google' })} /> {/* Biometric Auth */} <Button title="š Sign in with Face ID" onPress={() => signInWithBiometric({ promptMessage: 'Authenticate with Face ID', })} /> {/* Magic Link */} <Button title="Send Magic Link" onPress={() => sendMagicLink({ email: 'user@example.com', createIfNotExists: true, })} /> </View> ); }
š Authentication Methods
1ļøā£ OAuth/Social Login
Sign in with popular OAuth providers:
import { useZewstID } from '@zewstid/react-native'; function SocialLogin() { const { signInWithOAuth } = useZewstID(); return ( <> <Button title="Google" onPress={() => signInWithOAuth({ provider: 'google' })} /> <Button title="GitHub" onPress={() => signInWithOAuth({ provider: 'github' })} /> <Button title="Apple" onPress={() => signInWithOAuth({ provider: 'apple' })} /> <Button title="Microsoft" onPress={() => signInWithOAuth({ provider: 'microsoft' })} /> <Button title="Facebook" onPress={() => signInWithOAuth({ provider: 'facebook' })} /> </> ); }
Features:
- Automatic deep link handling
- PKCE for enhanced security
- Seamless mobile browser flow
- Returns to app automatically
2ļøā£ Biometric Authentication
Face ID, Touch ID, or Fingerprint authentication:
import { useZewstID } from '@zewstid/react-native'; import { useEffect, useState } from 'react'; import { Alert } from 'react-native'; function BiometricLogin() { const { signInWithBiometric, enableBiometric, isBiometricAvailable, isBiometricEnabled, } = useZewstID(); const [available, setAvailable] = useState(false); const [enabled, setEnabled] = useState(false); useEffect(() => { const checkBiometric = async () => { const isAvailable = await isBiometricAvailable(); const isEnabled = await isBiometricEnabled(); setAvailable(isAvailable); setEnabled(isEnabled); }; checkBiometric(); }, []); if (!available) { return <Text>Biometric authentication not available on this device</Text>; } return ( <> {/* Sign in with biometric */} <Button title="š Sign in with Face ID" onPress={async () => { try { await signInWithBiometric({ promptMessage: 'Authenticate to access your account', cancelButtonText: 'Cancel', fallbackToPasscode: true, }); } catch (error) { Alert.alert('Authentication Failed', error.message); } }} /> {/* Enable biometric for this device */} {!enabled && ( <Button title="Enable Face ID" onPress={async () => { await enableBiometric(); setEnabled(true); }} /> )} </> ); }
Supported Biometric Types:
- iOS: Face ID, Touch ID
- Android: Fingerprint, Face Unlock, Iris
Security:
- Credentials stored in device Keychain/KeyStore
- Never sent over the network
- Requires device unlock
- Automatic fallback to device passcode
3ļøā£ Magic Links
Passwordless email authentication:
import { useZewstID } from '@zewstid/react-native'; import { useState } from 'react'; import { TextInput, Button, Text } from 'react-native'; function MagicLinkLogin() { const { sendMagicLink } = useZewstID(); const [email, setEmail] = useState(''); const [sent, setSent] = useState(false); const handleSend = async () => { try { await sendMagicLink({ email, createIfNotExists: true, // Auto-create users redirectUrl: 'myapp://auth/magic-link', }); setSent(true); } catch (error) { Alert.alert('Error', error.message); } }; if (sent) { return ( <View> <Text>āļø Check your email!</Text> <Text>Click the magic link to sign in</Text> </View> ); } return ( <> <TextInput placeholder="Email" value={email} onChangeText={setEmail} autoCapitalize="none" keyboardType="email-address" /> <Button title="Send Magic Link" onPress={handleSend} /> </> ); }
4ļøā£ OTP Authentication
Email or SMS one-time passwords:
import { useZewstID } from '@zewstid/react-native'; import { useState } from 'react'; import { TextInput, Button } from 'react-native'; function OTPLogin() { const { sendOTP, verifyOTP } = useZewstID(); const [email, setEmail] = useState(''); const [code, setCode] = useState(''); const [step, setStep] = useState<'email' | 'code'>('email'); const handleSendOTP = async () => { try { await sendOTP({ channel: 'email', // or 'sms' emailOrPhone: email, createIfNotExists: true, }); setStep('code'); } catch (error) { Alert.alert('Error', error.message); } }; const handleVerifyOTP = async () => { try { await verifyOTP(code, email); // User is now authenticated } catch (error) { Alert.alert('Invalid Code', error.message); } }; if (step === 'email') { return ( <> <TextInput placeholder="Email or Phone" value={email} onChangeText={setEmail} keyboardType="email-address" /> <Button title="Send Code" onPress={handleSendOTP} /> </> ); } return ( <> <Text>Enter the 6-digit code sent to {email}</Text> <TextInput placeholder="000000" value={code} onChangeText={setCode} keyboardType="number-pad" maxLength={6} /> <Button title="Verify Code" onPress={handleVerifyOTP} /> </> ); }
5ļøā£ Password Authentication
Traditional email/password sign in:
import { useZewstID } from '@zewstid/react-native'; import { useState } from 'react'; import { TextInput, Button } from 'react-native'; function PasswordLogin() { const { signInWithPassword, signUpWithPassword } = useZewstID(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const handleSignIn = async () => { try { await signInWithPassword({ emailOrUsername: email, password, }); } catch (error) { Alert.alert('Sign In Failed', error.message); } }; const handleSignUp = async () => { try { await signUpWithPassword({ email, password, firstName: 'John', lastName: 'Doe', }); } catch (error) { Alert.alert('Sign Up Failed', error.message); } }; return ( <> <TextInput placeholder="Email" value={email} onChangeText={setEmail} autoCapitalize="none" keyboardType="email-address" /> <TextInput placeholder="Password" value={password} onChangeText={setPassword} secureTextEntry /> <Button title="Sign In" onPress={handleSignIn} /> <Button title="Sign Up" onPress={handleSignUp} /> </> ); }
šÆ React Hooks
useZewstID()
useZewstID()Main authentication hook with all methods:
const { // State session, // Current session with tokens user, // Current user isLoading, // Loading state isAuthenticated, // Boolean auth status error, // Last error // OAuth / Social signInWithOAuth, // Magic Link sendMagicLink, verifyMagicLink, // OTP sendOTP, verifyOTP, // Password signInWithPassword, signUpWithPassword, // Biometric signInWithBiometric, enableBiometric, disableBiometric, isBiometricAvailable, isBiometricEnabled, // Session Management signOut, updateUser, refreshSession, } = useZewstID();
useUser()
useUser()Shorthand to get current user:
const user = useUser(); if (user) { console.log(user.email, user.name, user.id); }
useSession()
useSession()Get current session with tokens:
const session = useSession(); if (session) { console.log(session.accessToken, session.expiresAt); }
useIsAuthenticated()
useIsAuthenticated()Check authentication status:
const isAuthenticated = useIsAuthenticated(); return isAuthenticated ? <HomeScreen /> : <LoginScreen />;
useRequireAuth()
useRequireAuth()Require authentication with redirect:
function ProtectedScreen({ navigation }) { useRequireAuth(() => { navigation.navigate('Login'); }); return <Text>Protected content</Text>; }
š§ Advanced Usage
Direct Client Access
For advanced use cases outside of React components:
import { ZewstIDClient } from '@zewstid/react-native'; const client = new ZewstIDClient({ apiUrl: 'https://api.zewstid.com', clientId: 'your-client-id', redirectUri: 'myapp://auth/callback', }); // Use client methods const session = await client.getSession(); await client.signInWithPassword({ emailOrUsername: 'user@example.com', password: 'password123', }); await client.signOut();
Custom Secure Storage
import { SecureStorage } from '@zewstid/react-native'; const storage = new SecureStorage('my-app-prefix'); // Store string await storage.setSecure('api_key', 'abc123'); const apiKey = await storage.getSecure('api_key'); // Store object await storage.setSecureJSON('user', { id: 1, name: 'John' }); const user = await storage.getSecureJSON('user'); // Delete await storage.deleteSecure('api_key'); // Clear all await storage.clearSecure();
Biometric Utilities
import { BiometricAuth } from '@zewstid/react-native'; const biometric = new BiometricAuth(); // Check availability and type const { available, biometryType } = await biometric.isAvailable(); console.log(biometryType); // 'FaceID' | 'TouchID' | 'Fingerprint' | 'Iris' // Authenticate with custom options const success = await biometric.authenticate({ promptMessage: 'Authenticate to continue', cancelButtonText: 'Cancel', fallbackToPasscode: true, disableDeviceFallback: false, }); // Get friendly biometric name const name = await biometric.getBiometricName(); // Returns: "Face ID", "Touch ID", "Fingerprint", etc.
Auth State Listener
Listen to authentication state changes:
<ZewstIDProvider config={config} onAuthStateChange={(session) => { if (session) { console.log('User signed in:', session.user); analytics.identify(session.user.id); // Navigate to home screen navigation.navigate('Home'); } else { console.log('User signed out'); analytics.reset(); // Navigate to login navigation.navigate('Login'); } }} > <App /> </ZewstIDProvider>
š¦ TypeScript
Full TypeScript support with comprehensive types:
import type { // Configuration ZewstIDConfig, // Session & User ZewstIDSession, ZewstIDUser, // Auth Options OAuthOptions, MagicLinkOptions, OTPOptions, PasswordSignInOptions, PasswordSignUpOptions, BiometricAuthOptions, // Utilities BiometricType, OAuthProvider, // Errors ZewstIDError, BiometricError, NetworkError, } from '@zewstid/react-native';
āļø Configuration
Full Configuration Options
<ZewstIDProvider config={{ // Required apiUrl: 'https://api.zewstid.com', clientId: 'your-client-id', redirectUri: 'myapp://auth/callback', // Optional clientSecret: 'your-secret', // For confidential clients scopes: ['openid', 'profile', 'email'], // OAuth scopes debug: __DEV__, // Enable debug logging storageKeyPrefix: 'myapp', // Custom storage prefix enableBiometric: true, // Enable biometric by default // Timeouts (milliseconds) requestTimeout: 30000, // API request timeout tokenRefreshBuffer: 60000, // Refresh tokens 1min before expiry }} onAuthStateChange={(session) => { // Handle authentication state changes }} > <App /> </ZewstIDProvider>
š”ļø Security
Built-in Security Features
- ā Secure Token Storage - Keychain (iOS) / KeyStore (Android)
- ā Auto Token Refresh - Tokens refreshed before expiry
- ā PKCE for OAuth - Enhanced OAuth security
- ā Biometric Encryption - Credentials encrypted with biometric keys
- ā SSL Pinning - Prevent man-in-the-middle attacks
- ā Jailbreak Detection - Optional security checks
Platform-Specific Security
iOS
- Keychain Security: Credentials stored in iOS Keychain
- Data Protection: Complete protection when device locked
- Biometric Storage: Keys protected by Secure Enclave
- App Store: Privacy policy must cover biometric usage
Android
- KeyStore Security: Hardware-backed key storage
- StrongBox: Additional security on supported devices
- Biometric Authentication: CryptoObject validation
- ProGuard: Recommended for release builds
šØ Error Handling
import { ZewstIDError, BiometricError, NetworkError, AuthenticationError, } from '@zewstid/react-native'; try { await signInWithBiometric(); } catch (error) { if (error instanceof BiometricError) { switch (error.code) { case 'USER_CANCEL': console.log('User cancelled biometric prompt'); break; case 'NOT_ENROLLED': console.log('No biometric enrolled on device'); break; case 'NOT_AVAILABLE': console.log('Biometric hardware not available'); break; default: console.log('Biometric error:', error.message); } } else if (error instanceof NetworkError) { Alert.alert('Network Error', 'Please check your internet connection'); } else if (error instanceof AuthenticationError) { Alert.alert('Authentication Failed', error.message); } else if (error instanceof ZewstIDError) { Alert.alert('Error', `${error.code}: ${error.message}`); } }
š± Platform-Specific Notes
iOS Requirements
- Minimum Version: iOS 13.0+
- Face ID: Add to Info.plist
NSFaceIDUsageDescription - Touch ID: Works automatically
- Universal Links: Configure for seamless OAuth redirects
- Keychain Sharing: Enable for multi-app authentication
Android Requirements
- Minimum SDK: 23 (Android 6.0)
- Biometric: permission required
USE_BIOMETRIC - ProGuard: Add keep rules for SDK classes
- Deep Links: Configure intent filters in manifest
- Google Play: Review auth flow requirements
ProGuard Rules (Android)
# ZewstID SDK -keep class com.zewstid.** { *; } -keepclassmembers class com.zewstid.** { *; } -dontwarn com.zewstid.**
š Examples
Complete Login Flow
import React, { useState } from 'react'; import { View, Button, TextInput, Alert } from 'react-native'; import { useZewstID } from '@zewstid/react-native'; export function CompleteLoginScreen() { const { signInWithOAuth, signInWithBiometric, signInWithPassword, sendMagicLink, isBiometricAvailable, } = useZewstID(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); return ( <View style={{ padding: 20 }}> {/* Social Login */} <Button title="Continue with Google" onPress={() => signInWithOAuth({ provider: 'google' })} /> <Button title="Continue with Apple" onPress={() => signInWithOAuth({ provider: 'apple' })} /> {/* Biometric */} {isBiometricAvailable() && ( <Button title="š Use Face ID" onPress={() => signInWithBiometric()} /> )} {/* Email/Password */} <TextInput placeholder="Email" value={email} onChangeText={setEmail} autoCapitalize="none" /> <TextInput placeholder="Password" value={password} onChangeText={setPassword} secureTextEntry /> <Button title="Sign In" onPress={() => signInWithPassword({ emailOrUsername: email, password, })} /> {/* Passwordless */} <Button title="Send Magic Link" onPress={() => sendMagicLink({ email })} /> </View> ); }
š Standalone Authenticator App (v0.3.0+)
Build your own authenticator app like Google Authenticator or Authy with full TOTP support.
QR Code Enrollment
Scan QR codes to add accounts to your authenticator app:
import { useEnrollment } from '@zewstid/react-native'; import { Camera } from 'expo-camera'; import { Alert } from 'react-native'; function QRScanner() { const { parseQRCode, completeEnrollment, isEnrolling, error } = useEnrollment(); const handleBarCodeScanned = async ({ data }) => { try { // Parse the QR code data const enrollmentData = parseQRCode(data); if (enrollmentData) { // Complete the enrollment (stores secret securely) const account = await completeEnrollment({ enrollmentData, deviceInfo: { name: 'My iPhone', platform: 'ios', }, }); Alert.alert('Success', `Added account: ${account.name}`); } } catch (err) { Alert.alert('Error', err.message); } }; return ( <Camera onBarCodeScanned={handleBarCodeScanned} barCodeScannerSettings={{ barCodeTypes: ['qr'], }} style={{ flex: 1 }} /> ); }
TOTP Code Generation
Generate TOTP codes locally with countdown timer:
import { useTOTPGenerator } from '@zewstid/react-native'; function AuthenticatorCode({ accountId }) { const { code, // Current 6-digit code remainingSeconds, // Seconds until refresh progress, // 0-100 percentage isConfigured, isLoading, error, } = useTOTPGenerator({ accountId, onCodeChange: (totpCode) => { console.log('New code:', totpCode.code); }, }); if (isLoading) return <Text>Loading...</Text>; if (!isConfigured) return <Text>TOTP not configured</Text>; return ( <View> <Text style={{ fontSize: 36, fontFamily: 'monospace' }}> {code.slice(0, 3)} {code.slice(3)} </Text> <Text>Refreshes in {remainingSeconds}s</Text> <View style={{ width: `${progress}%`, height: 4, backgroundColor: 'blue' }} /> </View> ); }
Pre-built TOTPCode Component
Use the pre-built component for a complete TOTP display:
import { TOTPCode } from '@zewstid/react-native'; function AccountItem({ account }) { return ( <TOTPCode accountId={account.id} accountName={account.name} showAccountName={true} showTimer={true} showProgress={true} copyOnPress={true} onCopy={(code) => console.log('Copied:', code)} onCodeChange={(code) => console.log('Code changed:', code)} /> ); }
TOTPCode Props:
| Prop | Type | Default | Description |
|---|---|---|---|
accountId | string | required | Account ID to generate code for |
accountName | string | - | Display name for the account |
showAccountName | boolean | true | Show account name above code |
showTimer | boolean | true | Show countdown timer |
showProgress | boolean | true | Show progress bar |
copyOnPress | boolean | true | Copy code when pressed |
onCopy | function | - | Callback when code is copied |
onCodeChange | function | - | Callback when code changes |
Account Management
Manage multiple authenticator accounts:
import { useAccounts } from '@zewstid/react-native'; import { FlatList, Text, Button, Alert } from 'react-native'; function AccountsList() { const { accounts, isLoading, error, fetchAccounts, removeAccount, } = useAccounts(); const handleRemove = async (accountId) => { Alert.alert( 'Remove Account', 'Are you sure? You will need to set up TOTP again.', [ { text: 'Cancel', style: 'cancel' }, { text: 'Remove', style: 'destructive', onPress: async () => { await removeAccount(accountId); }, }, ] ); }; if (isLoading) return <Text>Loading accounts...</Text>; return ( <FlatList data={accounts} keyExtractor={(item) => item.id} renderItem={({ item }) => ( <View> <TOTPCode accountId={item.id} accountName={item.name} /> <Button title="Remove" onPress={() => handleRemove(item.id)} /> </View> )} onRefresh={fetchAccounts} refreshing={isLoading} /> ); }
useEnrollment Hook
Hook for QR code enrollment and device registration.
Returns:
{ parseQRCode: (qrData: string) => EnrollmentQRData | null; completeEnrollment: (options: CompleteEnrollmentOptions) => Promise<AuthenticatorAccount>; isEnrolling: boolean; error: string | null; }
useAccounts Hook
Hook for managing authenticator accounts.
Returns:
{ accounts: AuthenticatorAccount[]; isLoading: boolean; error: string | null; fetchAccounts: () => Promise<void>; removeAccount: (accountId: string) => Promise<void>; getAccount: (accountId: string) => AuthenticatorAccount | undefined; }
useTOTPGenerator Hook
Hook for generating TOTP codes with timer.
Returns:
{ code: string; // Current 6-digit code remainingSeconds: number; // Seconds until next code progress: number; // 0-100 percentage remaining isConfigured: boolean; // Whether TOTP is set up isLoading: boolean; error: string | null; setupTOTP: () => void; // Navigate to setup }
Complete Authenticator App Example
import React, { useState } from 'react'; import { View, FlatList, Button, Modal } from 'react-native'; import { ZewstIDProvider, useAccounts, useEnrollment, TOTPCode, } from '@zewstid/react-native'; import { Camera } from 'expo-camera'; function AuthenticatorApp() { const { accounts, fetchAccounts, removeAccount } = useAccounts(); const { parseQRCode, completeEnrollment, isEnrolling } = useEnrollment(); const [showScanner, setShowScanner] = useState(false); const handleScan = async ({ data }) => { const enrollmentData = parseQRCode(data); if (enrollmentData) { await completeEnrollment({ enrollmentData, deviceInfo: { name: 'My Phone', platform: 'ios' }, }); setShowScanner(false); fetchAccounts(); } }; return ( <View style={{ flex: 1 }}> <FlatList data={accounts} keyExtractor={(item) => item.id} renderItem={({ item }) => ( <TOTPCode accountId={item.id} accountName={item.name} copyOnPress={true} /> )} /> <Button title="Add Account" onPress={() => setShowScanner(true)} /> <Modal visible={showScanner} animationType="slide"> <Camera onBarCodeScanned={handleScan} barCodeScannerSettings={{ barCodeTypes: ['qr'] }} style={{ flex: 1 }} /> <Button title="Cancel" onPress={() => setShowScanner(false)} /> </Modal> </View> ); } export default function App() { return ( <ZewstIDProvider config={{ apiUrl: 'https://api.zewstid.com', clientId: 'your-client-id', redirectUri: 'myauthapp://callback', }} > <AuthenticatorApp /> </ZewstIDProvider> ); }
š Documentation
- React Native SDK Guide
- API Reference
- TOTP Implementation Guide
- Push Authentication Guide
- Biometric Auth Guide
- Migration Guide
š Support
- š Documentation
- š¬ Discord Community
- š GitHub Issues
- š§ Email Support
š License
Proprietary - Copyright Ā© 2025 Zewst, Inc. All Rights Reserved.
š Credits
Built with:
- React Native - Mobile framework
- react-native-keychain - Secure storage
- react-native-biometrics - Biometric auth
- Expo - Developer tools
Made with ā¤ļø by the Zewst Team
Was this page helpful?
Let us know how we can improve our documentation