Implement Face ID authentication and update iOS configuration for biometric support
This commit is contained in:
21
docs/IOS_BIOMETRICS.md
Normal file
21
docs/IOS_BIOMETRICS.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
iOS Biometric / Device Lock Setup
|
||||||
|
|
||||||
|
To enable and test device biometric authentication on iOS:
|
||||||
|
|
||||||
|
- Ensure the Info.plist contains a Face ID usage description (`NSFaceIDUsageDescription`). This project already adds a default string.
|
||||||
|
- Install CocoaPods and native deps:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ios
|
||||||
|
bundle install # (if you use Bundler)
|
||||||
|
bundle exec pod install
|
||||||
|
```
|
||||||
|
|
||||||
|
- Open the workspace in Xcode (`ios/authenticationsdk.xcworkspace`) and build to a device or simulator. For Face ID testing use a simulator with Face ID enabled or a device with Face ID configured.
|
||||||
|
- The app uses `react-native-keychain` to prompt for biometrics / device passcode; no additional Xcode entitlements are required beyond the Info.plist usage string.
|
||||||
|
|
||||||
|
If biometrics are not available, the Keychain prompt can fall back to the device passcode when configured.
|
||||||
|
|
||||||
|
Testing notes:
|
||||||
|
- Simulator: Device > Face ID > Enrolled to simulate Face ID responses.
|
||||||
|
- Device: Make sure Face ID / Touch ID is configured in Settings.
|
||||||
@@ -35,6 +35,8 @@
|
|||||||
</dict>
|
</dict>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
<string></string>
|
<string></string>
|
||||||
|
<key>NSFaceIDUsageDescription</key>
|
||||||
|
<string>This app uses Face ID to secure access to sensitive features.</string>
|
||||||
<key>RCTNewArchEnabled</key>
|
<key>RCTNewArchEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
|
|||||||
@@ -1,16 +1,54 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { View, Text } from "react-native";
|
import { View, Text, Alert } from "react-native";
|
||||||
import { Camera, useCameraDevices } from "react-native-vision-camera";
|
import { Camera, useCameraDevices } from "react-native-vision-camera";
|
||||||
import { useFaceDetector } from "vision-camera-face-detector";
|
import { useFaceDetector } from "vision-camera-face-detector";
|
||||||
|
import * as Keychain from "react-native-keychain";
|
||||||
|
|
||||||
export default function FaceRecognition() {
|
export default function FaceRecognition() {
|
||||||
|
const [authenticated, setAuthenticated] = useState(false);
|
||||||
const devices = useCameraDevices();
|
const devices = useCameraDevices();
|
||||||
const device = devices.front;
|
const device = devices.front;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Camera.requestCameraPermission();
|
Camera.requestCameraPermission();
|
||||||
|
authenticateWithKeychain();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
async function authenticateWithKeychain() {
|
||||||
|
try {
|
||||||
|
// Try to retrieve a protected generic password which will trigger
|
||||||
|
// the system authentication prompt (Face ID / Touch ID / passcode).
|
||||||
|
const creds = await Keychain.getGenericPassword({
|
||||||
|
authenticationPrompt: { title: "Authenticate to unlock" },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (creds) {
|
||||||
|
setAuthenticated(true);
|
||||||
|
console.log("Authenticated via Keychain, credentials found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no credentials stored yet, create one protected by biometrics
|
||||||
|
await Keychain.setGenericPassword("user", "secure-token", {
|
||||||
|
accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY,
|
||||||
|
accessible: Keychain.ACCESSIBLE.WHEN_PASSCODE_SET_THIS_DEVICE_ONLY,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now request access which will prompt biometric/passcode
|
||||||
|
const creds2 = await Keychain.getGenericPassword({
|
||||||
|
authenticationPrompt: { title: "Authenticate to unlock" },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (creds2) {
|
||||||
|
setAuthenticated(true);
|
||||||
|
console.log("Authenticated after storing credentials.");
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
console.log("Authentication error:", e);
|
||||||
|
Alert.alert("Authentication failed", e?.message || String(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const faceDetector = useFaceDetector({
|
const faceDetector = useFaceDetector({
|
||||||
landmarkMode: "all",
|
landmarkMode: "all",
|
||||||
performanceMode: "fast",
|
performanceMode: "fast",
|
||||||
@@ -18,6 +56,13 @@ export default function FaceRecognition() {
|
|||||||
|
|
||||||
if (!device) return <Text>Loading camera...</Text>;
|
if (!device) return <Text>Loading camera...</Text>;
|
||||||
|
|
||||||
|
if (!authenticated)
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
|
||||||
|
<Text>Locked. Please authenticate to continue.</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
<Camera
|
<Camera
|
||||||
|
|||||||
Reference in New Issue
Block a user