Skip to main content

Push Authentication Guide

Enable Okta Verify-style push authentication in your mobile app.

Overview

Push authentication allows users to approve login requests by tapping a notification on their mobile device. When someone tries to log in from a web browser, a push notification is sent to the user's registered mobile device. The user taps the notification, sees authentication context (IP address, browser, location), and approves or denies the request.

How It Works

┌─────────────┐ 1. Sign in with Push ┌──────────────┐ │ Web Browser │─────────────────────────────────────▶│ API Gateway │ │ │ │ │ │ Enters │ 2. Push notification │ Looks up │ │ email │◀─────────────────────────────────────│ devices │ └─────────────┘ └──────┬───────┘ │ │ │ │ │ 5. Tokens │ 3. Push │ returned │ sent ▼ ▼ ✅ Logged in ┌──────────────────────────────┐ │ Mobile App │ │ (Your existing app) │ │ │ │ ┌────────────────────────┐ │ │ │ Login Request │ │ │ │ Browser: Chrome │ │ │ │ IP: 203.0.113.42 │ │ │ │ Location: SF, CA │ │ │ │ │ │ │ │ [Deny] [Approve] │ │ │ └────────────────────────┘ │ │ │ │ 4. User approves │ └──────────────────────────────┘

Prerequisites

  • React Native app (iOS and/or Android)
  • @zewstid/react-native
    v0.2.0 or later
  • Firebase project (for push notifications)
  • Apple Developer account (for iOS APNs)

Step 1: Install Dependencies

npm install @zewstid/react-native@latest --registry https://npm.zewstid.com/ npm install @react-native-firebase/app @react-native-firebase/messaging

Step 2: Configure Firebase

Android Setup

  1. Go to Firebase Console
  2. Create a new project or select existing
  3. Add Android app
  4. Download
    google-services.json
  5. Place in
    android/app/google-services.json

Update

android/build.gradle
:

buildscript { dependencies { classpath 'com.google.gms:google-services:4.4.0' } }

Update

android/app/build.gradle
:

apply plugin: 'com.google.gms.google-services'

iOS Setup

  1. In Firebase Console, add iOS app

  2. Download

    GoogleService-Info.plist

  3. Place in

    ios/YourApp/GoogleService-Info.plist

  4. Open Xcode:

    • Add capability: Push Notifications
    • Add capability: Background Modes → Remote notifications
  5. Create APNs key:

    • Go to Apple Developer
    • Create new key with APNs enabled
    • Download
      .p8
      file
    • Upload to Firebase Console → Cloud Messaging

Update

ios/YourApp/Info.plist
:

<key>UIBackgroundModes</key> <array> <string>remote-notification</string> </array>

Step 3: Add to Your App

3.1 Add AuthChallengeModal

In your root

App.tsx
:

import { ZewstIDProvider, AuthChallengeModal } from '@zewstid/react-native'; function App() { return ( <ZewstIDProvider config={{ apiUrl: 'https://api.zewstid.com/api/v1', clientId: 'your-client-id', redirectUri: 'myapp://auth/callback', }} > <YourApp /> {/* 👇 Add this one component */} <AuthChallengeModal brandColor="#007AFF" /> </ZewstIDProvider> ); }

That's it! The modal will automatically:

  • Listen for push notifications
  • Show the approval UI when a challenge arrives
  • Require biometric authentication (Face ID/Touch ID)
  • Handle approve/deny responses

3.2 Add Device Registration

In your Settings screen:

import { useRegisterDevice } from '@zewstid/react-native'; import { Button, Text, View, Alert } from 'react-native'; function SettingsScreen() { const { registerDevice, isRegistered, isRegistering, error } = useRegisterDevice(); const handleEnablePush = async () => { try { await registerDevice({ deviceName: 'My iPhone', // Or auto-detect }); Alert.alert( 'Success!', 'Push authentication enabled. You can now approve logins from this device.' ); } catch (err) { Alert.alert('Error', err.message); } }; return ( <View> {!isRegistered ? ( <Button title={isRegistering ? 'Registering...' : 'Enable Push Authentication'} onPress={handleEnablePush} disabled={isRegistering} /> ) : ( <Text>✅ Push authentication enabled</Text> )} {error && <Text style={{ color: 'red' }}>{error}</Text>} </View> ); }

Testing

Test the Complete Flow

  1. Register device:

    • Open your mobile app
    • Navigate to Settings
    • Tap "Enable Push Authentication"
    • Grant push notification permission
  2. Initiate login from web:

    • Open your web app in a browser
    • Click "Sign in with Push"
    • Enter your email address
  3. Approve on mobile:

    • Push notification arrives on your phone
    • Tap the notification
    • Modal shows login context
    • Tap "Approve"
    • Complete biometric authentication (Face ID/Touch ID)
  4. Verify login:

    • Web browser automatically logs you in
    • You're redirected to your dashboard

Security Features

All built-in and automatic:

  • Biometric verification required - Face ID/Touch ID/Fingerprint before approval
  • Challenge expiration - Automatic 2-minute timeout
  • Context display - Shows IP, browser, location for user verification
  • Rate limiting - 5 challenges per hour per user
  • WebSocket updates - Real-time status (no polling)
  • Encrypted tokens - AES-256-GCM in Redis

Advanced Usage

Custom Challenge Handling

If you need custom UI instead of the pre-built modal:

import { usePushNotifications } from '@zewstid/react-native'; import { useEffect } from 'react'; function CustomPushHandler() { const { challenge, respondToChallenge, isResponding } = usePushNotifications(); useEffect(() => { if (challenge) { // Custom logic here console.log('Challenge received:', challenge.context); } }, [challenge]); const handleApprove = async () => { await respondToChallenge(challenge.id, 'approved', true); }; const handleDeny = async () => { await respondToChallenge(challenge.id, 'denied'); }; if (!challenge) return null; return ( <View> <Text>Login from {challenge.context.browser}</Text> <Text>IP: {challenge.context.ipAddress}</Text> <Text>Location: {challenge.context.location}</Text> <Button title="Approve" onPress={handleApprove} disabled={isResponding} /> <Button title="Deny" onPress={handleDeny} disabled={isResponding} /> </View> ); }
<AuthChallengeModal brandColor="#FF6B00" // Your brand color onApprove={() => { console.log('User approved login'); // Track analytics, show success message, etc. }} onDeny={() => { console.log('User denied login'); // Track analytics, show warning, etc. }} />

API Reference

useRegisterDevice()

Hook for registering the device for push authentication.

Returns:

{ registerDevice: (options?: { deviceName?: string }) => Promise<string>; isRegistered: boolean; isRegistering: boolean; deviceId: string | null; error: string | null; }

Example:

const { registerDevice, isRegistered } = useRegisterDevice(); await registerDevice({ deviceName: 'My iPhone 15 Pro' });

usePushNotifications()

Hook for handling incoming push authentication challenges.

Returns:

{ challenge: AuthChallenge | null; respondToChallenge: ( challengeId: string, response: 'approved' | 'denied', biometricVerified?: boolean ) => Promise<void>; isResponding: boolean; error: string | null; }

Challenge Object:

{ id: string; context: { ipAddress: string; browser: string; location: string; timestamp: number; }; }

<AuthChallengeModal />

Pre-built modal component for approving/denying challenges.

Props:

{ onApprove?: () => void; onDeny?: () => void; brandColor?: string; // Default: '#007AFF' }

Troubleshooting

Push notifications not arriving

  1. Check Firebase configuration:

    • Verify
      google-services.json
      (Android) is in the correct location
    • Verify
      GoogleService-Info.plist
      (iOS) is added to Xcode
    • Check APNs key is uploaded to Firebase Console
  2. Check permissions:

    • Ensure push notification permission is granted
    • On iOS: Settings → Your App → Notifications
    • On Android: Settings → Apps → Your App → Notifications
  3. Verify device registration:

    const { isRegistered, deviceId } = useRegisterDevice(); console.log('Registered:', isRegistered, 'ID:', deviceId);

"Device not registered" error

The user hasn't completed device registration. Ensure

registerDevice()
is called after user logs in.

Biometric authentication failing

  1. Check device has biometrics enrolled (Face ID/Touch ID/Fingerprint)
  2. Verify app has biometric permission:
    • iOS:
      NSFaceIDUsageDescription
      in Info.plist
    • Android:
      USE_BIOMETRIC
      permission in AndroidManifest.xml

Challenge expired

Challenges expire after 2 minutes for security. User must approve within this window.

Best Practices

  1. Register device after login - Only authenticated users can register devices
  2. Show clear status - Use
    isRegistered
    to show enrollment status
  3. Handle errors gracefully - Show user-friendly error messages
  4. Test both platforms - iOS and Android have different push behaviors
  5. Monitor delivery - Track push notification delivery rates

FAQ

Q: Can I customize the modal UI? A: Yes, either customize the

brandColor
prop or use
usePushNotifications()
to build your own UI.

Q: Does this work offline? A: No, push authentication requires internet connectivity. Use biometric authentication for offline scenarios.

Q: How many devices can a user register? A: Unlimited. Users can register multiple devices (phone, tablet, etc.).

Q: Is this secure? A: Yes. All challenges require biometric verification, expire after 2 minutes, are rate-limited, and show full context for user verification.

Q: What happens if user denies? A: The web browser shows "Login denied" and the challenge is marked as denied in the database.

Next Steps

Was this page helpful?

Let us know how we can improve our documentation