Skip to main content

expo-app-integrity

Version: 55.0.6 A native module that helps assert app integrity on mobile platforms using Apple’s App Attest and Google’s Play Integrity APIs. Verify that your app hasn’t been tampered with and is running on a genuine device.

Installation

npx expo install expo-app-integrity

Usage

import * as AppIntegrity from 'expo-app-integrity';

// Check if integrity checking is available
const isAvailable = await AppIntegrity.isAvailableAsync();

if (isAvailable) {
  // Request attestation
  const token = await AppIntegrity.getAttestationAsync('challenge-from-server');
  
  // Send token to your server for verification
  await verifyWithServer(token);
}

API Reference

Methods

isAvailableAsync()
() => Promise<boolean>
Checks if app integrity verification is availableReturns true on iOS 14+ and Android with Play Services
const available = await AppIntegrity.isAvailableAsync();
if (!available) {
  console.log('Integrity checking not available');
}
getAttestationAsync(challenge)
(challenge: string) => Promise<string>
Requests attestation tokenParameters:
  • challenge (string): Challenge string from your server
Returns: Attestation token to verify on server
const challenge = await fetchChallengeFromServer();
const token = await AppIntegrity.getAttestationAsync(challenge);

// Send token to server for verification
const isValid = await verifyTokenOnServer(token);

Examples

Basic Integrity Check

import * as AppIntegrity from 'expo-app-integrity';

async function verifyAppIntegrity() {
  const isAvailable = await AppIntegrity.isAvailableAsync();
  
  if (!isAvailable) {
    console.log('App integrity checking not supported');
    return false;
  }

  try {
    // Get challenge from your server
    const challenge = await fetch('https://api.example.com/challenge')
      .then(res => res.text());

    // Get attestation token
    const token = await AppIntegrity.getAttestationAsync(challenge);

    // Verify token with your server
    const response = await fetch('https://api.example.com/verify', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ token }),
    });

    const { valid } = await response.json();
    return valid;
  } catch (error) {
    console.error('Integrity check failed:', error);
    return false;
  }
}

Protect API Calls

import * * AppIntegrity from 'expo-app-integrity';

async function makeSecureAPICall(endpoint: string, data: any) {
  // Get attestation before making sensitive API call
  const challenge = Date.now().toString();
  const attestation = await AppIntegrity.getAttestationAsync(challenge);

  const response = await fetch(endpoint, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Attestation': attestation,
      'X-Challenge': challenge,
    },
    body: JSON.stringify(data),
  });

  return response.json();
}

Complete Example with Server Verification

import * as AppIntegrity from 'expo-app-integrity';
import { useState, useEffect } from 'react';
import { View, Button, Text, StyleSheet, ActivityIndicator } from 'react-native';

export default function IntegrityExample() {
  const [isAvailable, setIsAvailable] = useState(false);
  const [isVerified, setIsVerified] = useState<boolean | null>(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    checkAvailability();
  }, []);

  async function checkAvailability() {
    const available = await AppIntegrity.isAvailableAsync();
    setIsAvailable(available);
  }

  async function verifyIntegrity() {
    setLoading(true);
    setIsVerified(null);

    try {
      // Step 1: Get challenge from server
      const challengeResponse = await fetch(
        'https://your-api.com/integrity/challenge'
      );
      const { challenge } = await challengeResponse.json();

      // Step 2: Get attestation token
      const token = await AppIntegrity.getAttestationAsync(challenge);

      // Step 3: Verify with server
      const verifyResponse = await fetch(
        'https://your-api.com/integrity/verify',
        {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ token, challenge }),
        }
      );

      const { valid } = await verifyResponse.json();
      setIsVerified(valid);
    } catch (error) {
      console.error('Verification error:', error);
      setIsVerified(false);
    } finally {
      setLoading(false);
    }
  }

  if (!isAvailable) {
    return (
      <View style={styles.container}>
        <Text style={styles.message}>
          App integrity checking is not available on this device
        </Text>
      </View>
    );
  }

  return (
    <View style={styles.container}>
      <Text style={styles.title}>App Integrity Check</Text>

      {loading ? (
        <ActivityIndicator size="large" />
      ) : (
        <>
          <Button title="Verify App Integrity" onPress={verifyIntegrity} />

          {isVerified !== null && (
            <View style={styles.result}>
              {isVerified ? (
                <>
                  <Text style={styles.success}>✓ Verified</Text>
                  <Text>App integrity check passed</Text>
                </>
              ) : (
                <>
                  <Text style={styles.error}>✗ Failed</Text>
                  <Text>App integrity check failed</Text>
                </>
              )}
            </View>
          )}
        </>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
    gap: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
  },
  message: {
    fontSize: 16,
    textAlign: 'center',
    color: '#666',
  },
  result: {
    alignItems: 'center',
    gap: 10,
  },
  success: {
    fontSize: 48,
    color: '#4CAF50',
  },
  error: {
    fontSize: 48,
    color: '#F44336',
  },
});

Platform Support

PlatformAPI UsedMinimum Version
iOSApp AttestiOS 14+
AndroidPlay IntegrityAndroid 4.4+ with Play Services
Web❌ Not available-

Server-Side Verification

iOS (App Attest)

// Node.js example with Apple's App Attest API
const jwt = require('jsonwebtoken');
const { appleAppAttest } = require('apple-app-attest');

async function verifyiOSAttestation(token, challenge) {
  try {
    const decoded = jwt.decode(token, { complete: true });
    
    // Verify with Apple's servers
    const result = await appleAppAttest.verifyAttestation({
      attestation: token,
      challenge,
      bundleId: 'com.yourapp.bundle',
    });

    return result.valid;
  } catch (error) {
    console.error('iOS attestation verification failed:', error);
    return false;
  }
}

Android (Play Integrity)

// Node.js example with Google Play Integrity API
const { google } = require('googleapis');

async function verifyAndroidAttestation(token, challenge) {
  try {
    const playIntegrity = google.playintegrity('v1');
    
    const response = await playIntegrity.decodeIntegrityToken({
      packageName: 'com.yourapp.package',
      requestBody: { integrityToken: token },
    });

    // Check integrity verdict
    const { appIntegrity, deviceIntegrity } = response.data.tokenPayloadExternal;
    
    return (
      appIntegrity.appRecognitionVerdict === 'PLAY_RECOGNIZED' &&
      deviceIntegrity.deviceRecognitionVerdict.includes('MEETS_DEVICE_INTEGRITY')
    );
  } catch (error) {
    console.error('Android attestation verification failed:', error);
    return false;
  }
}

Configuration

iOS

No additional configuration required. App Attest is automatically available on iOS 14+.

Android

Add to app.json:
{
  "expo": {
    "android": {
      "package": "com.yourapp.package"
    },
    "plugins": [
      [
        "expo-app-integrity",
        {
          "androidCloudProjectNumber": "YOUR_PROJECT_NUMBER"
        }
      ]
    ]
  }
}
Enable Play Integrity API in Google Cloud Console.

Use Cases

  • Anti-Tampering: Detect if app has been modified or repackaged
  • Emulator Detection: Identify if app is running on emulator
  • Root/Jailbreak Detection: Detect rooted or jailbroken devices
  • API Protection: Verify app integrity before sensitive operations
  • License Verification: Ensure app is from official store
  • Fraud Prevention: Prevent abuse from modified apps

Best Practices

  1. Server Verification: Always verify attestation tokens on your server
  2. Fresh Challenges: Use unique, time-limited challenges
  3. Graceful Degradation: Handle unavailable integrity checks gracefully
  4. Rate Limiting: Limit attestation requests to prevent abuse
  5. Secure Storage: Store attestation keys securely
  6. Error Handling: Handle errors without revealing security details
Never rely solely on client-side integrity checks. Always verify attestation tokens on your secure server.

Limitations

  • iOS: Requires iOS 14 or later
  • Android: Requires Google Play Services
  • Rate Limits: Both platforms have rate limits on attestation requests
  • No Web Support: Not available for web platforms
  • Not Foolproof: Can be bypassed by sophisticated attackers
App integrity checks provide an additional layer of security but are not foolproof. Use them as part of a comprehensive security strategy.

Resources