flutter-permission-handler icon indicating copy to clipboard operation
flutter-permission-handler copied to clipboard

Biometrics (Face ID & Touch ID) Permission

Open alisadibekov opened this issue 3 years ago • 2 comments

🚀 Feature Requests

download

Biometrics (Touch ID + Face ID) permission in flutter_permission_handler package

Contextualize the feature

In order to authenticate users faster, developers use different authentication solutions. One of them is Biometric Authentication. A software might have to ask for biometrics permission while the app is running. Biometrics permission in flutter_permission_handler package could be helpful feature for developers.

Describe the feature

We could use biometrics permission as other types of permissions. For example, request popup and check status of the requested permission. if (await Permission.biometrics.request().isGranted) { // Do something helpful } var status = await Permission.camera.status;

Platforms affected (mark all that apply)

  • ✅ :iphone: iOS
  • ✅ :robot: Android

Thanks.

alisadibekov avatar Jan 14 '22 09:01 alisadibekov

Hello, any updates on this? Thank you

nlqsang avatar Jul 21 '22 08:07 nlqsang

Up this issue, please.

didifend avatar Jul 29 '22 04:07 didifend

Any update on this? This is the only thing which is missing from this Amazing package.

rahul-dezerv avatar Mar 16 '23 18:03 rahul-dezerv

Guys any update on this?

MomenYasser avatar Apr 01 '23 20:04 MomenYasser

+1

wildsurfer avatar May 13 '23 20:05 wildsurfer

+1

leagonzalez avatar May 30 '23 16:05 leagonzalez

+1

b-cancel avatar Jun 29 '23 21:06 b-cancel

@alisadibekov and others,

Android and iOS both do not require specific permission handling to access the biometric sensors. The only thing required is to add some configuration to the AndroidManifest.xml or Info.plist files informing the respective App stores that the application makes use of the biometric sensors.

For this reason there is no need to implement addition logic in the permission_handler plugin and I will close this issue. If there is something I overlooked, feel free to comment on this issue and make sure to point to the documentation for the specific platform.

mvanbeusekom avatar Jul 03 '23 08:07 mvanbeusekom

Hi, @mvanbeusekom

I followed the example of local_auth package, iOS still requires user permission to use biometrics for authenticate.

Reproduce steps:

  1. Create flutter project
2. Change main.dart as follow

// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore_for_file: public_member_api_docs, avoid_print

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:local_auth/local_auth.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final LocalAuthentication auth = LocalAuthentication();
  _SupportState _supportState = _SupportState.unknown;
  bool? _canCheckBiometrics;
  List<BiometricType>? _availableBiometrics;
  String _authorized = 'Not Authorized';
  bool _isAuthenticating = false;

  @override
  void initState() {
    super.initState();
    auth.isDeviceSupported().then(
          (bool isSupported) => setState(() => _supportState = isSupported
              ? _SupportState.supported
              : _SupportState.unsupported),
        );
  }

  Future<void> _checkBiometrics() async {
    late bool canCheckBiometrics;
    try {
      canCheckBiometrics = await auth.canCheckBiometrics;
    } on PlatformException catch (e) {
      canCheckBiometrics = false;
      print(e);
    }
    if (!mounted) {
      return;
    }

    setState(() {
      _canCheckBiometrics = canCheckBiometrics;
    });
  }

  Future<void> _getAvailableBiometrics() async {
    late List<BiometricType> availableBiometrics;
    try {
      availableBiometrics = await auth.getAvailableBiometrics();
    } on PlatformException catch (e) {
      availableBiometrics = <BiometricType>[];
      print(e);
    }
    if (!mounted) {
      return;
    }

    setState(() {
      _availableBiometrics = availableBiometrics;
    });
  }

  Future<void> _authenticate() async {
    bool authenticated = false;
    try {
      setState(() {
        _isAuthenticating = true;
        _authorized = 'Authenticating';
      });
      authenticated = await auth.authenticate(
        localizedReason: 'Let OS determine authentication method',
        options: const AuthenticationOptions(
          stickyAuth: true,
        ),
      );
      setState(() {
        _isAuthenticating = false;
      });
    } on PlatformException catch (e) {
      print(e);
      setState(() {
        _isAuthenticating = false;
        _authorized = 'Error - ${e.message}';
      });
      return;
    }
    if (!mounted) {
      return;
    }

    setState(
        () => _authorized = authenticated ? 'Authorized' : 'Not Authorized');
  }

  Future<void> _authenticateWithBiometrics() async {
    bool authenticated = false;
    try {
      setState(() {
        _isAuthenticating = true;
        _authorized = 'Authenticating';
      });
      authenticated = await auth.authenticate(
        localizedReason:
            'Scan your fingerprint (or face or whatever) to authenticate',
        options: const AuthenticationOptions(
          stickyAuth: true,
          biometricOnly: true,
        ),
      );
      setState(() {
        _isAuthenticating = false;
        _authorized = 'Authenticating';
      });
    } on PlatformException catch (e) {
      print(e);
      setState(() {
        _isAuthenticating = false;
        _authorized = 'Error - ${e.message}';
      });
      return;
    }
    if (!mounted) {
      return;
    }

    final String message = authenticated ? 'Authorized' : 'Not Authorized';
    setState(() {
      _authorized = message;
    });
  }

  Future<void> _cancelAuthentication() async {
    await auth.stopAuthentication();
    setState(() => _isAuthenticating = false);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: ListView(
          padding: const EdgeInsets.only(top: 30),
          children: <Widget>[
            Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                if (_supportState == _SupportState.unknown)
                  const CircularProgressIndicator()
                else if (_supportState == _SupportState.supported)
                  const Text('This device is supported')
                else
                  const Text('This device is not supported'),
                const Divider(height: 100),
                Text('Can check biometrics: $_canCheckBiometrics\n'),
                ElevatedButton(
                  onPressed: _checkBiometrics,
                  child: const Text('Check biometrics'),
                ),
                const Divider(height: 100),
                Text('Available biometrics: $_availableBiometrics\n'),
                ElevatedButton(
                  onPressed: _getAvailableBiometrics,
                  child: const Text('Get available biometrics'),
                ),
                const Divider(height: 100),
                Text('Current State: $_authorized\n'),
                if (_isAuthenticating)
                  ElevatedButton(
                    onPressed: _cancelAuthentication,
                    // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later.
                    // ignore: prefer_const_constructors
                    child: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: const <Widget>[
                        Text('Cancel Authentication'),
                        Icon(Icons.cancel),
                      ],
                    ),
                  )
                else
                  Column(
                    children: <Widget>[
                      ElevatedButton(
                        onPressed: _authenticate,
                        // TODO(goderbauer): Make this const when this package requires Flutter 3.8 or later.
                        // ignore: prefer_const_constructors
                        child: Row(
                          mainAxisSize: MainAxisSize.min,
                          children: const <Widget>[
                            Text('Authenticate'),
                            Icon(Icons.perm_device_information),
                          ],
                        ),
                      ),
                      ElevatedButton(
                        onPressed: _authenticateWithBiometrics,
                        child: Row(
                          mainAxisSize: MainAxisSize.min,
                          children: <Widget>[
                            Text(_isAuthenticating
                                ? 'Cancel'
                                : 'Authenticate: biometrics only'),
                            const Icon(Icons.fingerprint),
                          ],
                        ),
                      ),
                    ],
                  ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

enum _SupportState {
  unknown,
  supported,
  unsupported,
}
3. Add description to Info.plist

<key>NSFaceIDUsageDescription</key>
<string>This app need to scan your fingerprint or face to authenticate.</string>

Actual result:

App request user permission for the first use of biometrics.

https://github.com/Baseflow/flutter-permission-handler/assets/76845271/fdce96c6-9bb9-440c-a933-11cee7ea66f8


Flutter doctor

[✓] Flutter (Channel stable, 3.7.12, on macOS 13.4.1 22F82 darwin-x64, locale en-TH)
    • Flutter version 3.7.12 on channel stable at /Users/kittipongtheothaisong/fvm/versions/3.7.12
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 4d9e56e694 (3 months ago), 2023-04-17 21:47:46 -0400
    • Engine revision 1a65d409c7
    • Dart version 2.19.6
    • DevTools version 2.20.1

[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
    • Android SDK at /Users/kittipongtheothaisong/Library/Android/sdk
    • Platform android-33, build-tools 32.1.0-rc1
    • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7590822)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 14.3.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 14E300c
    • CocoaPods version 1.11.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2021.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7590822)

[✓] IntelliJ IDEA Community Edition (version 2021.3.3)
    • IntelliJ at /Applications/IntelliJ IDEA CE.app
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart

[✓] VS Code (version 1.79.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.66.0

[✓] Connected device (3 available)
    • The C (mobile)  • ************************* • ios            • iOS 16.5.1 20F75
    • macOS (desktop) • macos                     • darwin-x64     • macOS 13.4.1 22F82 darwin-x64
    • Chrome (web)    • chrome                    • web-javascript • Google Chrome 114.0.5735.198
    ! Error: The C is busy: Fetching debug symbols for The C. Xcode will continue when The C is finished. (code -10)

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!

Phone Info:

iOS version: 16.5.1 Model: iPhone 12 Pro

MrKnos avatar Jul 04 '23 03:07 MrKnos

@MrKnos, looking into the iOS Local Authentication SDK documentation, there doesn't seem to be any explicit method to request or check permissions. This means that Apple internally will handle requesting permissions to access FaceID. This is also mentioned in this SO post:

iOS won't prompt the user for permission until the first call to [LAContext evaluatePolicy:localizedReason:reply:].

As per my understanding there is no reliable way to check and request permissions.

mvanbeusekom avatar Jul 04 '23 06:07 mvanbeusekom