Урок 05

Биометрия и безопасность

Face ID, Touch ID, отпечаток пальца — защита приложения.

Expo Local Authentication

npx expo install expo-local-authentication

Проверка доступности

import * as LocalAuthentication from 'expo-local-authentication';

const hasHardware = await LocalAuthentication.hasHardwareAsync();
const isEnrolled = await LocalAuthentication.isEnrolledAsync();
const supportedTypes = await LocalAuthentication.supportedAuthenticationTypesAsync();
// [FINGERPRINT, FACIAL_RECOGNITION, IRIS]

Аутентификация

async function authenticate() {
    const result = await LocalAuthentication.authenticateAsync({
        promptMessage: 'Войдите в приложение',
        cancelLabel: 'Отмена',
        disableDeviceFallback: false,
        fallbackLabel: 'Использовать пароль',
    });

    if (result.success) {
        // вход разрешён
    }
}

Экран блокировки

function AppLock({ children }: { children: React.ReactNode }) {
    const [unlocked, setUnlocked] = useState(false);

    useEffect(() => {
        async function check() {
            const enrolled = await LocalAuthentication.isEnrolledAsync();
            if (!enrolled) {
                setUnlocked(true);
                return;
            }

            const result = await LocalAuthentication.authenticateAsync({
                promptMessage: 'Войдите',
            });
            setUnlocked(result.success);
        }
        check();
    }, []);

    if (!unlocked) return <LockScreen onUnlock={() => setUnlocked(true)} />;
    return children;
}

При возврате в приложение

import { AppState } from 'react-native';

useEffect(() => {
    const sub = AppState.addEventListener('change', (state) => {
        if (state === 'background') {
            setUnlocked(false);
        }
    });
    return () => sub.remove();
}, []);

SecureStore

Токены — в SecureStore, не в AsyncStorage:

import * as SecureStore from 'expo-secure-store';

await SecureStore.setItemAsync('token', token, {
    keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});

Опции:

  • WHEN_UNLOCKED — доступен только при разблокированном устройстве
  • WHEN_UNLOCKED_THIS_DEVICE_ONLY — не синхронизируется через iCloud
  • AFTER_FIRST_UNLOCK — доступен после первой разблокировки

Скриншоты и запись

npx expo install expo-screen-capture
import * as ScreenCapture from 'expo-screen-capture';

// Запретить скриншоты
await ScreenCapture.preventScreenCaptureAsync();

// Отследить скриншот
const sub = ScreenCapture.addScreenshotListener(() => {
    console.log('сделан скриншот');
});

Используется в банковских приложениях.

Сертификаты

Приложение должно проверять SSL-сертификаты. По умолчанию React Native это делает.

Для дополнительной защиты — certificate pinning:

npx expo install expo-build-properties

В app.json:

{
    "plugins": [
        [
            "expo-build-properties",
            {
                "android": {
                    "usesCleartextTraffic": false
                }
            }
        ]
    ]
}

Не хранить в коде

  • API-ключи (кроме публичных)
  • Пароли
  • Секретные ключи

Переменные окружения EXPO_PUBLIC_* попадают в бандл. Не клади туда секреты.

Работа с API

  • Только HTTPS
  • Токены в Authorization
  • Валидация ответов
  • Таймауты
  • Обработка 401

Итоги

  • expo-local-authentication для биометрии
  • AppState для блокировки в фоне
  • SecureStore с опциями
  • expo-screen-capture для защиты
  • HTTPS обязательно
  • Секреты — на сервере