Skip to main content

@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!

npm version License: Proprietary

✨ 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 -
    <TOTPCode />
    with progress indicator

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
)

<key>CFBundleURLTypes</key> <array> <dict> <key>CFBundleURLSchemes</key> <array> <string>myapp</string> </array> </dict> </array>

Android (
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

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()

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()

Shorthand to get current user:

const user = useUser(); if (user) { console.log(user.email, user.name, user.id); }

useSession()

Get current session with tokens:

const session = useSession(); if (session) { console.log(session.accessToken, session.expiresAt); }

useIsAuthenticated()

Check authentication status:

const isAuthenticated = useIsAuthenticated(); return isAuthenticated ? <HomeScreen /> : <LoginScreen />;

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
    NSFaceIDUsageDescription
    to Info.plist
  • 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:
    USE_BIOMETRIC
    permission required
  • 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:

PropTypeDefaultDescription
accountId
stringrequiredAccount ID to generate code for
accountName
string-Display name for the account
showAccountName
booleantrueShow account name above code
showTimer
booleantrueShow countdown timer
showProgress
booleantrueShow progress bar
copyOnPress
booleantrueCopy 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

šŸ†˜ Support

šŸ“œ License

Proprietary - Copyright Ā© 2025 Zewst, Inc. All Rights Reserved.

šŸ™ Credits

Built with:


Made with ā¤ļø by the Zewst Team

Was this page helpful?

Let us know how we can improve our documentation