Implement Secure device lock authentication (biometrics) with offline App PIN support

This commit is contained in:
mansi-dev
2026-01-17 23:54:33 +05:30
parent e4f91128ed
commit 6b328f1733
4 changed files with 142 additions and 0 deletions

70
src/screen/LockScreen.tsx Normal file
View File

@@ -0,0 +1,70 @@
import React, { useState } from 'react';
import {
View,
Text,
TouchableOpacity,
TextInput,
Alert,
} from 'react-native';
import { verifyPin } from '../auth/AppPinService';
import { unlockWithDevice, unlockApp } from '../auth/AuthManager';
const LockScreen = ({ onUnlock }: any) => {
const [pin, setPin] = useState('');
const handlePinUnlock = async () => {
const valid = await verifyPin(pin);
if (valid) {
await unlockApp();
onUnlock();
} else {
Alert.alert('Wrong PIN');
}
};
const handleDeviceUnlock = async () => {
const res = await unlockWithDevice();
if (res.success) {
await unlockApp();
onUnlock();
} else {
Alert.alert('Authentication Failed');
}
};
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
<Text style={{ fontSize: 20, marginBottom: 10 }}>
Enter App PIN
</Text>
<TextInput
keyboardType="number-pad"
secureTextEntry
maxLength={4}
value={pin}
onChangeText={setPin}
style={{
borderWidth: 1,
padding: 12,
borderRadius: 6,
marginBottom: 10,
}}
/>
<TouchableOpacity onPress={handlePinUnlock}>
<Text style={{ color: 'blue', marginBottom: 20 }}>
Unlock with PIN
</Text>
</TouchableOpacity>
<TouchableOpacity onPress={handleDeviceUnlock}>
<Text style={{ color: 'green' }}>
Unlock with Device Lock
</Text>
</TouchableOpacity>
</View>
);
};
export default LockScreen;

View File

@@ -0,0 +1,31 @@
import React, { useState } from 'react';
import { View, TextInput, Text, TouchableOpacity } from 'react-native';
import { savePin } from '../auth/AppPinService';
const SetPinScreen = ({ onDone }: any) => {
const [pin, setPin] = useState('');
const save = async () => {
await savePin(pin);
onDone();
};
return (
<View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
<Text>Create App PIN</Text>
<TextInput
secureTextEntry
keyboardType="number-pad"
maxLength={4}
value={pin}
onChangeText={setPin}
style={{ borderWidth: 1, padding: 12, marginVertical: 10 }}
/>
<TouchableOpacity onPress={save}>
<Text style={{ color: 'blue' }}>Save PIN</Text>
</TouchableOpacity>
</View>
);
};
export default SetPinScreen;

View File

@@ -0,0 +1,20 @@
import * as Keychain from 'react-native-keychain';
const PIN_KEY = 'APP_PIN';
export const savePin = async (pin: string) => {
await Keychain.setGenericPassword(PIN_KEY, pin, {
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED,
});
};
export const verifyPin = async (inputPin: string) => {
const creds = await Keychain.getGenericPassword();
if (!creds) return false;
return creds.password === inputPin;
};
export const isPinSet = async () => {
const creds = await Keychain.getGenericPassword();
return !!creds;
};

View File

@@ -0,0 +1,21 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { deviceAuthenticate } from './DeviceAuth';
const LOCK_KEY = 'APP_LOCKED';
export const lockApp = async () => {
await AsyncStorage.setItem(LOCK_KEY, 'true');
};
export const unlockApp = async () => {
await AsyncStorage.removeItem(LOCK_KEY);
};
export const isAppLocked = async () => {
const v = await AsyncStorage.getItem(LOCK_KEY);
return v === 'true';
};
export const unlockWithDevice = async () => {
return await deviceAuthenticate();
};