Implement Secure device lock authentication (biometrics) with offline App PIN support
This commit is contained in:
70
src/screen/LockScreen.tsx
Normal file
70
src/screen/LockScreen.tsx
Normal 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;
|
||||||
31
src/screen/SetPinScreen.tsx
Normal file
31
src/screen/SetPinScreen.tsx
Normal 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;
|
||||||
20
src/screen/authorised/AppPinService.ts
Normal file
20
src/screen/authorised/AppPinService.ts
Normal 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;
|
||||||
|
};
|
||||||
21
src/screen/authorised/AuthManager.ts
Normal file
21
src/screen/authorised/AuthManager.ts
Normal 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();
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user