@aparajita/capacitor-biometric-auth 6.0.1 β†’ 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,24 +2,19 @@
2
2
 
3
3
  # capacitor-biometric-auth  [![npm version](https://badge.fury.io/js/@aparajita%2Fcapacitor-biometric-auth.svg)](https://badge.fury.io/js/@aparajita%2Fcapacitor-biometric-auth)
4
4
 
5
- This plugin for [Capacitor 5](https://capacitorjs.com) provides access to native biometry on iOS and Android. It supports every type of biometry and every configuration option on both platforms. In addition, biometry is simulated on the web so you can test your logic without making any changes to your code.
5
+ This plugin for [Capacitor 5](https://capacitorjs.com) provides access to native biometry and device credentials on iOS and Android. It supports every type of biometry and every configuration option on both platforms. In addition, biometry and device credentials are simulated on the web so you can test your logic without making any changes to your code.
6
6
 
7
- πŸ‘‰ **NOTE:** This plugin only works with Capacitor 5. If you are upgrading from the Capacitor 2 version, note that the plugin name has changed to `BiometricAuth`.
7
+ πŸ›‘ **BREAKING CHANGES:**
8
8
 
9
- πŸ›‘ **BREAKING CHANGE:** If you are upgrading from a version prior to 6.0.0, please note that [`authenticate()`](#authenticate) now throws an instance of `BiometryError`, and `BiometryError.code` is now typed as [`BiometryErrorType`](#biometryerrortype).
10
-
11
- ## Demos
12
-
13
- Here is `capacitor-biometric-auth` running on the [demo app](https://github.com/aparajita/capacitor-biometric-auth-demo) on both iOS and Android.
14
-
15
- | iOS | Android |
16
- | :----------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- |
17
- | <video src="https://user-images.githubusercontent.com/22218/182895212-5f7bfa39-6db1-4149-859b-85cff7012903.mp4" width="352" /> | <video src="https://user-images.githubusercontent.com/22218/182898192-d16243b8-3671-4c32-9e25-5e37feeb43d4.mp4" width="365" /> |
9
+ - If you are upgrading from a version prior to 6.0.0, please note that [`authenticate()`](#authenticate) now throws an instance of `BiometryError`, and `BiometryError.code` is now typed as [`BiometryErrorType`](#biometryerrortype).
10
+ - If you are upgrading from a version prior to 7.0.0, please note that [`authenticate()`](#authenticate) will _immediately_ present a prompt for device credentials if `deviceIsSecure` is true, `allowDeviceCredentials` is true, and no biometry of the requested strength is available.
18
11
 
19
12
  ## Installation
20
13
 
21
14
  ```sh
22
15
  pnpm add @aparajita/capacitor-biometric-auth
16
+ npm install @aparajita/capacitor-biometric-auth
17
+ yarn add @aparajita/capacitor-biometric-auth
23
18
  ```
24
19
 
25
20
  Not using [pnpm](https://pnpm.js.org/)? You owe it to yourself to give it a try. It’s faster, better with monorepos, and uses _way, way_ less disk space than the alternatives.
@@ -42,11 +37,13 @@ The API is extensively documented in the [TypeScript definitions file](src/defin
42
37
 
43
38
  ### Checking availability
44
39
 
45
- Before giving the user the option to use biometry (such as displaying a biometry icon), call [`checkBiometry()`](#checkbiometry) and inspect the [`CheckBiometryResult`](#checkbiometryresult) to see what (if any) biometry is available on the device. Note the following:
40
+ Before giving the user the option to use biometry (such as displaying a biometry icon), call [`checkBiometry()`](#checkbiometry) and inspect the [`CheckBiometryResult`](#checkbiometryresult) to see what (if any) biometry and/or device credentials are available on the device. Note the following:
46
41
 
47
42
  - `isAvailable` may be `false` but `biometryType` may indicate the presence of biometry on the device. This occurs if the current user is not enrolled in biometry, or if biometry has been disabled for the current app. In such cases the `reason` and `code` will tell you why.
48
43
 
49
- - `biometryTypes` may contain more than one type of biometry. This occurs on Android devices that support multiple types of biometry. In such cases the `biometryType` will indicate the primary (most secure) type of biometry, and the `biometryTypes` array will contain all of the biometry types supported by the device. Note that Android only guarantees that one of the types is actually available.
44
+ - On iOS, `isAvailable` and `strongBiometryIsAvailable` will always have the same value. On Android, `isAvailable` will be `true` if _any_ type of biometry is available, but `strongBiometryIsAvailable` will be `true` only if strong biometry is available. For example, on a typical device, if the device supports both fingerprint and face authentication, `isAvailable` will be `true` if either is enrolled, but `strongBiometryIsAvailable` will be `true` only if fingerprint authentication is enrolled.
45
+
46
+ - `biometryTypes` may contain more than one type of biometry. This occurs on Android devices that support multiple types of biometry. In such cases `biometryType` will indicate the primary (most secure) type of biometry, and the `biometryTypes` array will contain all of the biometry types supported by the device. Note that Android only guarantees that one of the types is actually available.
50
47
 
51
48
  Because the availability of biometry can change while your app is in the background, it’s important to check availability when your app resumes. By calling [`addResumeListener()`](#addresumelistener) you can register a callback that is passed a [`CheckBiometryResult`](#checkbiometryresult) when your app resumes.
52
49
 
@@ -96,7 +93,11 @@ If authentication succeeds, the Promise resolves. If authentication fails, the P
96
93
  #### Example
97
94
 
98
95
  ```typescript
99
- import { BiometryError, BiometryErrorType } from './definitions'
96
+ import {
97
+ AndroidBiometryStrength,
98
+ BiometryError,
99
+ BiometryErrorType,
100
+ } from './definitions'
100
101
 
101
102
  async function authenticate(): Promise<void> {
102
103
  try {
@@ -108,6 +109,7 @@ async function authenticate(): Promise<void> {
108
109
  androidTitle: 'Biometric login',
109
110
  androidSubtitle: 'Log in using biometric authentication',
110
111
  androidConfirmationRequired: false,
112
+ androidBiometryStrength: AndroidBiometryStrength.weak,
111
113
  })
112
114
  } catch (error) {
113
115
  // error is always an instance of BiometryError.
@@ -133,7 +135,7 @@ On iOS, Touch ID and Face ID are supported.
133
135
 
134
136
  ### Android
135
137
 
136
- On Android, fingerprint, face, and iris authentication are supported. Note that if a device supports more than one type of biometry, the plugin will only present the primary (most secure) type, which is determined by the system.
138
+ On Android, fingerprint, face, and iris authentication are supported. Note that if a device supports more than one type of biometry, the plugin will initially present the primary (most secure) available type, which is determined by the system.
137
139
 
138
140
  ## API
139
141
 
@@ -141,6 +143,8 @@ On Android, fingerprint, face, and iris authentication are supported. Note that
141
143
 
142
144
  - [`checkBiometry()`](#checkbiometry)
143
145
  - [`setBiometryType(...)`](#setbiometrytype)
146
+ - [`setBiometryIsEnrolled(...)`](#setbiometryisenrolled)
147
+ - [`setDeviceIsSecure(...)`](#setdeviceissecure)
144
148
  - [`authenticate(...)`](#authenticate)
145
149
  - [`addResumeListener(...)`](#addresumelistener)
146
150
  - [Interfaces](#interfaces)
@@ -168,14 +172,42 @@ Check to see what biometry type (if any) is available.
168
172
  ### setBiometryType(...)
169
173
 
170
174
  ```typescript
171
- setBiometryType(type: BiometryType | string | undefined) => Promise<void>
175
+ setBiometryType(type: BiometryType | string | Array<BiometryType | string> | undefined) => Promise<void>
176
+ ```
177
+
178
+ web only<br><br>On the web, this method allows you to dynamically simulate different types of biometry. You may either pass <a href="#biometrytype">`BiometryType`</a> enums or the string names of the <a href="#biometrytype">`BiometryType`</a> enums. If undefined or a string is passed and it isn't a valid value, nothing happens.
179
+
180
+ | Param | Type |
181
+ | :---- | :----------------------------------------------------------------------------------------------------------- |
182
+ | type | string \| <a href="#biometrytype">BiometryType</a> \| (string \| <a href="#biometrytype">BiometryType</a>)[] |
183
+
184
+ ---
185
+
186
+ ### setBiometryIsEnrolled(...)
187
+
188
+ ```typescript
189
+ setBiometryIsEnrolled(isSecure: boolean) => Promise<void>
172
190
  ```
173
191
 
174
- web only<br><br>On the web, this method allows you to dynamically simulate different types of biometry. You may either pass a <a href="#biometrytype">`BiometryType`</a> enum or the string name of a <a href="#biometrytype">`BiometryType`</a>. If a string is passed and it isn't a valid value, nothing happens.
192
+ web only<br><br>On the web, this method allows you to dynamically simulate whether or not the user has enrolled in biometry.
175
193
 
176
- | Param | Type |
177
- | :---- | :------------------------------------------------- |
178
- | type | string \| <a href="#biometrytype">BiometryType</a> |
194
+ | Param | Type |
195
+ | :------- | :------ |
196
+ | isSecure | boolean |
197
+
198
+ ---
199
+
200
+ ### setDeviceIsSecure(...)
201
+
202
+ ```typescript
203
+ setDeviceIsSecure(isSecure: boolean) => Promise<void>
204
+ ```
205
+
206
+ web only<br><br>On the web, this method allows you to dynamically simulate whether or not the user has secured the device with a PIN, pattern or passcode.
207
+
208
+ | Param | Type |
209
+ | :------- | :------ |
210
+ | isSecure | boolean |
179
211
 
180
212
  ---
181
213
 
@@ -213,25 +245,86 @@ Register a function that will be called when the app resumes. The function will
213
245
 
214
246
  #### CheckBiometryResult
215
247
 
216
- | Prop | Type | Description |
217
- | :------------ | :------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
218
- | isAvailable | boolean | True if the device has biometric authentication capability and the current user has enrolled in some form of biometry. |
219
- | biometryType | <a href="#biometrytype">BiometryType</a> | The primary type of biometry available on the device. If the device supports both fingerprint and face authentication, this will be <a href="#biometrytype">`BiometryType.touchId`</a>. |
220
- | biometryTypes | BiometryType[] | All of the biometry types supported by the device (currently only Android devices support multiple biometry types). If no biometry is available, this will be an empty array. If multiple types are supported, Android only guarantees that one of them is actually available. |
221
- | reason | string | If biometry is not available and the system gives a reason why, it will be returned here. Otherwise it's an empty string. |
222
- | code | <a href="#biometryerrortype">BiometryErrorType</a> | If biometry is not available, the error code will be returned here. Otherwise it's an empty string. The error code will be one of the <a href="#biometryerrortype">`BiometryErrorType`</a> enum values, and is consistent across platforms. |
248
+ | Prop | Type | Description |
249
+ | :------------------------ | :------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
250
+ | isAvailable | boolean | True if the device supports _at least_ weak biometric authentication and the current user has enrolled in some form of biometry. Note that if `strongBiometryIsAvailable` is true, this will also be true. |
251
+ | strongBiometryIsAvailable | boolean | True if the device has strong biometric authentication capability and the current user has enrolled in that strong biometry.<br><br>On iOS this value and `isAvailable` will always be the same, since iOS only supports strong biometry.<br><br>On Android, for example, if the device supports both fingerprint and face authentication, and the user has enrolled only in face authentication, and Android considers face authentication on that device to be weak, then `isAvailable` will be true but this value will be false. |
252
+ | biometryType | <a href="#biometrytype">BiometryType</a> | The primary (most secure) type of biometry supported by the device. Note that _supported_ is not the same as _available_, which requires the biometry to be enrolled. |
253
+ | biometryTypes | BiometryType[] | All of the biometry types supported by the hardware on the device (currently only Android devices support multiple biometry types). If no biometry is supported, this will be an empty array.<br><br>Note that _supported_ is not the same as _available_, which requires the biometry to be enrolled. |
254
+ | deviceIsSecure | boolean | Returns true if the device is secure. On iOS, this means that the device has a passcode set. On Android, this means that the device has a PIN, pattern, or password set. |
255
+ | reason | string | If biometry is not available and the system gives a reason why, it will be returned here. Otherwise it's an empty string. |
256
+ | code | <a href="#biometryerrortype">BiometryErrorType</a> | If biometry is not available, the error code will be returned here. Otherwise it's an empty string. The error code will be one of the <a href="#biometryerrortype">`BiometryErrorType`</a> enum values, and is consistent across platforms. |
257
+
258
+ #### Array
259
+
260
+ | Prop | Type | Description |
261
+ | :----- | :----- | :----------------------------------------------------------------------------------------------------- |
262
+ | length | number | Gets or sets the length of the array. This is a number one higher than the highest index in the array. |
263
+
264
+ | Method | Signature | Description |
265
+ | :-------------------------------------------------------------------------- | :----------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- |
266
+ | **toString** | () =&gt; string | Returns a string representation of an array. |
267
+ | **toLocaleString** | () =&gt; string | Returns a string representation of an array. The elements are converted to string using their toLocaleString methods. |
268
+ | **pop** | () =&gt; T \| undefined | Removes the last element from an array and returns it. |
269
+ | If the array is empty, undefined is returned and the array is not modified. |
270
+ | **push** | (...items: T[]) =&gt; number | Appends new elements to the end of an array, and returns the new length of the array. |
271
+ | **concat** | (...items: <a href="#concatarray">ConcatArray</a>&lt;T&gt;[]) =&gt; T[] | Combines two or more arrays. |
272
+ | This method returns a new array without modifying any existing arrays. |
273
+ | **concat** | (...items: (T \| <a href="#concatarray">ConcatArray</a>&lt;T&gt;)[]) =&gt; T[] | Combines two or more arrays. |
274
+ | This method returns a new array without modifying any existing arrays. |
275
+ | **join** | (separator?: string) =&gt; string | Adds all the elements of an array into a string, separated by the specified separator string. |
276
+ | **reverse** | () =&gt; T[] | Reverses the elements in an array in place. |
277
+ | This method mutates the array and returns a reference to the same array. |
278
+ | **shift** | () =&gt; T \| undefined | Removes the first element from an array and returns it. |
279
+ | If the array is empty, undefined is returned and the array is not modified. |
280
+ | **slice** | (start?: number, end?: number) =&gt; T[] | Returns a copy of a section of an array. |
281
+
282
+ For both start and end, a negative index can be used to indicate an offset from the end of the array.
283
+ For example, -2 refers to the second to last element of the array. |
284
+ | **sort** | (compareFn?: ((a: T, b: T) =&gt; number) \| undefined) =&gt; this | Sorts an array in place.
285
+ This method mutates the array and returns a reference to the same array. |
286
+ | **splice** | (start: number, deleteCount?: number) =&gt; T[] | Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements. |
287
+ | **splice** | (start: number, deleteCount: number, ...items: T[]) =&gt; T[] | Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements. |
288
+ | **unshift** | (...items: T[]) =&gt; number | Inserts new elements at the start of an array, and returns the new length of the array. |
289
+ | **indexOf** | (searchElement: T, fromIndex?: number) =&gt; number | Returns the index of the first occurrence of a value in an array, or -1 if it is not present. |
290
+ | **lastIndexOf** | (searchElement: T, fromIndex?: number) =&gt; number | Returns the index of the last occurrence of a specified value in an array, or -1 if it is not present. |
291
+ | **every** | &lt;S extends T&gt;(predicate: (value: T, index: number, array: T[]) =&gt; value is S, thisArg?: any) =&gt; this is S[] | Determines whether all the members of an array satisfy the specified test. |
292
+ | **every** | (predicate: (value: T, index: number, array: T[]) =&gt; unknown, thisArg?: any) =&gt; boolean | Determines whether all the members of an array satisfy the specified test. |
293
+ | **some** | (predicate: (value: T, index: number, array: T[]) =&gt; unknown, thisArg?: any) =&gt; boolean | Determines whether the specified callback function returns true for any element of an array. |
294
+ | **forEach** | (callbackfn: (value: T, index: number, array: T[]) =&gt; void, thisArg?: any) =&gt; void | Performs the specified action for each element in an array. |
295
+ | **map** | &lt;U&gt;(callbackfn: (value: T, index: number, array: T[]) =&gt; U, thisArg?: any) =&gt; U[] | Calls a defined callback function on each element of an array, and returns an array that contains the results. |
296
+ | **filter** | &lt;S extends T&gt;(predicate: (value: T, index: number, array: T[]) =&gt; value is S, thisArg?: any) =&gt; S[] | Returns the elements of an array that meet the condition specified in a callback function. |
297
+ | **filter** | (predicate: (value: T, index: number, array: T[]) =&gt; unknown, thisArg?: any) =&gt; T[] | Returns the elements of an array that meet the condition specified in a callback function. |
298
+ | **reduce** | (callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) =&gt; T) =&gt; T | Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function. |
299
+ | **reduce** | (callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) =&gt; T, initialValue: T) =&gt; T | |
300
+ | **reduce** | &lt;U&gt;(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) =&gt; U, initialValue: U) =&gt; U | Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function. |
301
+ | **reduceRight** | (callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) =&gt; T) =&gt; T | Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function. |
302
+ | **reduceRight** | (callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) =&gt; T, initialValue: T) =&gt; T | |
303
+ | **reduceRight** | &lt;U&gt;(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) =&gt; U, initialValue: U) =&gt; U | Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function. |
304
+
305
+ #### ConcatArray
306
+
307
+ | Prop | Type |
308
+ | :----- | :----- |
309
+ | length | number |
310
+
311
+ | Method | Signature |
312
+ | :-------- | :--------------------------------------- |
313
+ | **join** | (separator?: string) =&gt; string |
314
+ | **slice** | (start?: number, end?: number) =&gt; T[] |
223
315
 
224
316
  #### AuthenticateOptions
225
317
 
226
- | Prop | Type | Description |
227
- | :-------------------------- | :------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
228
- | reason | string | The reason for requesting authentication. Displays in the authentication dialog presented to the user. If not supplied, a default message is displayed. |
229
- | cancelTitle | string | iOS: The system presents a cancel button during biometric authentication to let the user abort the authentication attempt. The button appears every time the system asks the user to present a finger registered with Touch ID. For Face ID, the button only appears if authentication fails and the user is prompted to try again. Either way, the user can stop trying to authenticate by tapping the button.<br><br>Android: The text for the negative button. This would typically be used as a "Cancel" button, but may be also used to show an alternative method for authentication, such as a screen that asks for a backup password.<br><br>Default: "Cancel" |
230
- | allowDeviceCredential | boolean | If true, allows for authentication using device unlock credentials. Default is false.<br><br>iOS: If biometry is available, enrolled, and not disabled, the system uses that first. After the first Touch ID failure or second Face ID failure, if `iosFallbackTitle` is not an empty string, a fallback button appears in the authentication dialog. If the user taps the fallback button, the system prompts the user for the device passcode or the user’s password. If `iosFallbackTitle` is an empty string, no fallback button will appear.<br><br>If biometry is not available, enrolled and enabled, and a passcode is set, the system immediately prompts the user for the device passcode or user’s password. Authentication fails with the error code `passcodeNotSet` if the device passcode isn’t enabled.<br><br>If a passcode is not set on the device, a `passcodeNotSet` error is returned.<br><br>The system disables passcode authentication after 6 unsuccessful attempts, with progressively increasing delays between attempts.<br><br>The title of the fallback button may be customized by setting `iosFallbackTitle` to a non-empty string.<br><br>Android: The user will first be prompted to authenticate with biometrics, but also given the option to authenticate with their device PIN, pattern, or password by tapping a button in the authentication dialog. The title of the button is supplied by the system. |
231
- | iosFallbackTitle | string | The system presents a fallback button when biometric authentication fails β€” for example, because the system doesn’t recognize the presented finger, or after several failed attempts to recognize the user’s face.<br><br>If `allowDeviceCredential` is false, tapping this button dismisses the authentication dialog and returns the error code userFallback. If undefined, the localized system default title is used. Passing an empty string hides the fallback button completely.<br><br>If `allowDeviceCredential` is true and this is undefined, the localized system default title is used. |
232
- | androidTitle | string | Title for the Android dialog. If not supplied, the system default is used. |
233
- | androidSubtitle | string | Subtitle for the Android dialog. If not supplied, the system default is used. |
234
- | androidConfirmationRequired | boolean | If not set, defaults to true.<br><br>For information on this setting, see https://developer.android.com/reference/android/hardware/biometrics/BiometricPrompt.Builder#setConfirmationRequired(boolean). |
318
+ | Prop | Type | Description |
319
+ | :-------------------------- | :------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
320
+ | reason | string | Displays the reason for requesting authentication in the authentication dialog presented to the user.<br><br>Default: System default |
321
+ | cancelTitle | string | iOS: The system presents a cancel button during biometric authentication to let the user abort the authentication attempt. The button appears every time the system asks the user to present a finger registered with Touch ID. For Face ID, the button only appears if authentication fails and the user is prompted to try again. Either way, the user can stop trying to authenticate by tapping the button.<br><br>Android: The text for the negative button. This would typically be used as a "Cancel" button, but may be also used to show an alternative method for authentication, such as a screen that asks for a backup password.<br><br>Default: "Cancel" |
322
+ | allowDeviceCredential | boolean | If true, allows for authentication using device unlock credentials.<br><br>Default: false.<br><br>iOS: If biometry is available, enrolled, and not disabled, the system uses that first. After the first Touch ID failure or second Face ID failure, if `iosFallbackTitle` is not an empty string, a fallback button appears in the authentication dialog. If the user taps the fallback button, the system prompts the user for the device passcode or the user’s password. If `iosFallbackTitle` is an empty string, no fallback button will appear.<br><br>If no biometry is enrolled and enabled, and a passcode is set, the system immediately prompts the user for the device passcode or user’s password. Authentication fails with the error code `passcodeNotSet` if the device passcode isn’t enabled.<br><br>If a passcode is not set on the device, a `passcodeNotSet` error is returned.<br><br>The system disables passcode authentication after 6 unsuccessful attempts, with progressively increasing delays between attempts.<br><br>The title of the fallback button may be customized by setting `iosFallbackTitle` to a non-empty string.<br><br>Android: The user will first be prompted to authenticate with biometrics, but also given the option to authenticate with their device PIN, pattern, or password by tapping a button in the authentication dialog. The title of the button is supplied by the system. |
323
+ | iosFallbackTitle | string | The system presents a fallback button when biometric authentication fails β€” for example, because the system doesn’t recognize the presented finger, or after several failed attempts to recognize the user’s face.<br><br>If `allowDeviceCredential` is false, tapping this button dismisses the authentication dialog and returns the error code userFallback. If undefined, the localized system default title is used. Passing an empty string hides the fallback button completely.<br><br>If `allowDeviceCredential` is true and this is undefined, the localized system default title is used. |
324
+ | androidTitle | string | Title for the Android dialog. If not supplied, the system default is used. |
325
+ | androidSubtitle | string | Subtitle for the Android dialog. If not supplied, the system default is used. |
326
+ | androidConfirmationRequired | boolean | Determines if successful weak biometric authentication must be confirmed.<br><br>For information on this setting, see https://developer.android.com/reference/android/hardware/biometrics/BiometricPrompt.Builder#setConfirmationRequired(boolean).<br><br>Default: `true` |
327
+ | androidBiometryStrength | <a href="#androidbiometrystrength">AndroidBiometryStrength</a> | Set the strength of Android biometric authentication that will be accepted.<br><br>Default: <a href="#androidbiometrystrength">`AndroidBiometryStrength.weak`</a> |
235
328
 
236
329
  #### PluginListenerHandle
237
330
 
@@ -251,14 +344,14 @@ The signature of the callback passed to `addResumeListener()`.
251
344
 
252
345
  #### BiometryType
253
346
 
254
- | Members | Description |
255
- | :------------------------ | :---------------------------------------------- |
256
- | none | No biometry is available |
257
- | touchId | iOS Touch ID is available |
258
- | faceId | iOS Face ID is available |
259
- | fingerprintAuthentication | Android fingerprint authentication is available |
260
- | faceAuthentication | Android face authentication is available |
261
- | irisAuthentication | Android iris authentication is available |
347
+ | Members | Description |
348
+ | :------------------------ | :--------------------------------- |
349
+ | none | |
350
+ | touchId | iOS Touch ID |
351
+ | faceId | iOS Face ID |
352
+ | fingerprintAuthentication | Android fingerprint authentication |
353
+ | faceAuthentication | Android face authentication |
354
+ | irisAuthentication | Android iris authentication |
262
355
 
263
356
  #### BiometryErrorType
264
357
 
@@ -278,5 +371,12 @@ The signature of the callback passed to `addResumeListener()`.
278
371
  | biometryNotEnrolled | 'biometryNotEnrolled' |
279
372
  | noDeviceCredential | 'noDeviceCredential' |
280
373
 
374
+ #### AndroidBiometryStrength
375
+
376
+ | Members | Description |
377
+ | :------ | :---------------------------------------------------- |
378
+ | weak | `authenticate()` will present any available biometry. |
379
+ | strong | `authenticate()` will only present strong biometry. |
380
+
281
381
  </docgen-api>
282
382
  </div>
@@ -1,15 +1,13 @@
1
1
  package com.aparajita.capacitor.biometricauth;
2
2
 
3
3
  import android.annotation.SuppressLint;
4
- import android.app.KeyguardManager;
5
4
  import android.content.Intent;
6
- import android.hardware.biometrics.BiometricManager;
7
5
  import android.os.Build;
8
6
  import android.os.Bundle;
9
7
  import android.os.Handler;
10
- import android.util.Log;
11
8
  import androidx.annotation.NonNull;
12
9
  import androidx.appcompat.app.AppCompatActivity;
10
+ import androidx.biometric.BiometricManager;
13
11
  import androidx.biometric.BiometricPrompt;
14
12
  import java.util.concurrent.Executor;
15
13
 
@@ -33,24 +31,37 @@ public class AuthActivity extends AppCompatActivity {
33
31
 
34
32
  BiometricPrompt.PromptInfo.Builder builder =
35
33
  new BiometricPrompt.PromptInfo.Builder();
34
+
36
35
  Intent intent = getIntent();
37
36
  String title = intent.getStringExtra(BiometricAuthNative.TITLE);
38
37
  String subtitle = intent.getStringExtra(BiometricAuthNative.SUBTITLE);
39
38
  String description = intent.getStringExtra(BiometricAuthNative.REASON);
40
- allowDeviceCredential = false;
41
39
 
42
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
43
- // Android docs say we should check if the device is secure before enabling device credential fallback
44
- KeyguardManager manager = (KeyguardManager) getSystemService(
45
- KEYGUARD_SERVICE
46
- );
40
+ int authenticators = intent.getIntExtra(
41
+ BiometricAuthNative.BIOMETRIC_STRENGTH,
42
+ BiometricManager.Authenticators.BIOMETRIC_WEAK
43
+ );
47
44
 
48
- if (manager.isDeviceSecure()) {
49
- allowDeviceCredential =
50
- intent.getBooleanExtra(BiometricAuthNative.DEVICE_CREDENTIAL, false);
51
- }
45
+ allowDeviceCredential =
46
+ intent.getBooleanExtra(BiometricAuthNative.DEVICE_CREDENTIAL, false);
47
+
48
+ // Android docs say that BIOMETRIC_STRONG | DEVICE_CREDENTIAL cannot be used on API 28-29.
49
+ // If that is the case, fall back to BIOMETRIC_WEAK.
50
+ if (
51
+ authenticators == BiometricManager.Authenticators.BIOMETRIC_STRONG &&
52
+ allowDeviceCredential &&
53
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
54
+ Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q
55
+ ) {
56
+ authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK;
52
57
  }
53
58
 
59
+ if (allowDeviceCredential) {
60
+ authenticators |= BiometricManager.Authenticators.DEVICE_CREDENTIAL;
61
+ }
62
+
63
+ builder.setAllowedAuthenticators(authenticators);
64
+
54
65
  // The title must be non-null and non-empty
55
66
  if (title == null || title.isEmpty()) {
56
67
  title = "Authenticate";
@@ -58,19 +69,7 @@ public class AuthActivity extends AppCompatActivity {
58
69
 
59
70
  builder.setTitle(title).setSubtitle(subtitle).setDescription(description);
60
71
 
61
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
62
- int authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK;
63
-
64
- if (allowDeviceCredential) {
65
- authenticators |= BiometricManager.Authenticators.DEVICE_CREDENTIAL;
66
- }
67
-
68
- builder.setAllowedAuthenticators(authenticators);
69
- } else {
70
- builder.setDeviceCredentialAllowed(allowDeviceCredential);
71
- }
72
-
73
- // Android docs say that negative button text should not be set if device credential is allowed
72
+ // Android docs say that negative button text should not be set if device credential is allowed.
74
73
  if (!allowDeviceCredential) {
75
74
  String negativeButtonText = intent.getStringExtra(
76
75
  BiometricAuthNative.CANCEL_TITLE
@@ -2,9 +2,10 @@ package com.aparajita.capacitor.biometricauth;
2
2
 
3
3
  import android.annotation.SuppressLint;
4
4
  import android.app.Activity;
5
+ import android.app.KeyguardManager;
6
+ import android.content.Context;
5
7
  import android.content.Intent;
6
8
  import android.content.pm.PackageManager;
7
- import android.os.Build;
8
9
  import androidx.activity.result.ActivityResult;
9
10
  import androidx.biometric.BiometricManager;
10
11
  import androidx.biometric.BiometricPrompt;
@@ -29,6 +30,7 @@ public class BiometricAuthNative extends Plugin {
29
30
  public static final String SUBTITLE = "androidSubtitle";
30
31
  public static final String REASON = "reason";
31
32
  public static final String CANCEL_TITLE = "cancelTitle";
33
+ public static final String BIOMETRIC_STRENGTH = "biometricStrength";
32
34
  public static final String DEVICE_CREDENTIAL = "allowDeviceCredential";
33
35
  public static final String CONFIRMATION_REQUIRED =
34
36
  "androidConfirmationRequired";
@@ -91,27 +93,31 @@ public class BiometricAuthNative extends Plugin {
91
93
 
92
94
  private ArrayList<BiometryType> biometryTypes;
93
95
 
94
- /**
95
- * Check the device's availability and type of biometric authentication.
96
- */
97
- @PluginMethod
98
- public void checkBiometry(PluginCall call) {
99
- call.resolve(checkDeviceBiometry());
96
+ private int getAuthenticatorFromCall(PluginCall call) {
97
+ Integer value = call.getInt(
98
+ "androidBiometryStrength",
99
+ BiometryStrength.WEAK.ordinal()
100
+ );
101
+ int authenticator = BiometricManager.Authenticators.BIOMETRIC_WEAK;
102
+
103
+ if (value == BiometryStrength.STRONG.ordinal()) {
104
+ authenticator = BiometricManager.Authenticators.BIOMETRIC_STRONG;
105
+ }
106
+
107
+ return authenticator;
100
108
  }
101
109
 
102
110
  /**
103
111
  * Check the device's availability and type of biometric authentication.
104
112
  */
105
- private JSObject checkDeviceBiometry() {
113
+ @PluginMethod
114
+ public void checkBiometry(PluginCall call) {
106
115
  BiometricManager manager = BiometricManager.from(getContext());
107
- int biometryResult;
108
116
 
109
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
110
- biometryResult =
111
- manager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK);
112
- } else {
113
- biometryResult = manager.canAuthenticate();
114
- }
117
+ // First check for weak biometry or better.
118
+ int biometryResult = manager.canAuthenticate(
119
+ BiometricManager.Authenticators.BIOMETRIC_WEAK
120
+ );
115
121
 
116
122
  JSObject result = new JSObject();
117
123
  result.put(
@@ -119,17 +125,36 @@ public class BiometricAuthNative extends Plugin {
119
125
  biometryResult == BiometricManager.BIOMETRIC_SUCCESS
120
126
  );
121
127
 
128
+ // Now check for strong biometry.
129
+ biometryResult =
130
+ manager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG);
131
+ result.put(
132
+ "strongBiometryIsAvailable",
133
+ biometryResult == BiometricManager.BIOMETRIC_SUCCESS
134
+ );
135
+
122
136
  biometryTypes = getDeviceBiometryTypes();
123
137
  result.put("biometryType", biometryTypes.get(0).getType());
124
138
 
125
139
  JSArray returnTypes = new JSArray();
126
140
 
127
141
  for (BiometryType type : biometryTypes) {
128
- returnTypes.put(type.getType());
142
+ if (type != BiometryType.NONE) {
143
+ returnTypes.put(type.getType());
144
+ }
129
145
  }
130
146
 
131
147
  result.put("biometryTypes", returnTypes);
132
148
 
149
+ KeyguardManager keyguardManager = (KeyguardManager) this.getContext()
150
+ .getSystemService(Context.KEYGUARD_SERVICE);
151
+
152
+ if (keyguardManager != null) {
153
+ result.put("deviceIsSecure", keyguardManager.isKeyguardSecure());
154
+ } else {
155
+ result.put("deviceIsSecure", false);
156
+ }
157
+
133
158
  String reason = "";
134
159
 
135
160
  switch (biometryResult) {
@@ -165,7 +190,7 @@ public class BiometricAuthNative extends Plugin {
165
190
 
166
191
  result.put("reason", reason);
167
192
  result.put("code", errorCode);
168
- return result;
193
+ call.resolve(result);
169
194
  }
170
195
 
171
196
  private ArrayList<BiometryType> getDeviceBiometryTypes() {
@@ -196,23 +221,12 @@ public class BiometricAuthNative extends Plugin {
196
221
  */
197
222
  @PluginMethod
198
223
  public void internalAuthenticate(final PluginCall call) {
199
- // Make sure biometry is available
200
- JSObject checkResult = checkDeviceBiometry();
201
-
202
- if (Boolean.FALSE.equals(checkResult.getBoolean("isAvailable", false))) {
203
- call.reject(
204
- checkResult.getString("reason", ""),
205
- checkResult.getString("code", "")
206
- );
207
- return;
208
- }
209
-
210
- // The result of an intent is supposed to have the package name as a prefix
224
+ // The result of an intent is supposed to have the package name as a prefix.
211
225
  RESULT_EXTRA_PREFIX = getContext().getPackageName() + ".";
212
226
 
213
227
  Intent intent = new Intent(getContext(), AuthActivity.class);
214
228
 
215
- // Pass the options to the activity
229
+ // Pass the options to the activity.
216
230
  intent.putExtra(
217
231
  TITLE,
218
232
  call.getString(TITLE, biometryNameMap.get(biometryTypes.get(0)))
@@ -224,6 +238,7 @@ public class BiometricAuthNative extends Plugin {
224
238
  DEVICE_CREDENTIAL,
225
239
  call.getBoolean(DEVICE_CREDENTIAL, false)
226
240
  );
241
+ intent.putExtra(BIOMETRIC_STRENGTH, getAuthenticatorFromCall(call));
227
242
 
228
243
  if (call.hasOption(CONFIRMATION_REQUIRED)) {
229
244
  intent.putExtra(
@@ -257,7 +272,7 @@ public class BiometricAuthNative extends Plugin {
257
272
  return;
258
273
  }
259
274
 
260
- // Convert the string result type to an enum
275
+ // Convert the string result type to an enum.
261
276
  Intent data = result.getData();
262
277
  String resultTypeName = null;
263
278
 
@@ -298,7 +313,7 @@ public class BiometricAuthNative extends Plugin {
298
313
 
299
314
  switch (resultType) {
300
315
  case SUCCESS -> call.resolve();
301
- // Biometry was successfully presented but was not recognized
316
+ // Biometry was successfully presented but was not recognized.
302
317
  case FAILURE -> call.reject(errorMessage, BIOMETRIC_FAILURE);
303
318
  // The user cancelled, the system cancelled, or some error occurred.
304
319
  // If the user cancelled, errorMessage is the text of the "negative" button,
@@ -0,0 +1,6 @@
1
+ package com.aparajita.capacitor.biometricauth;
2
+
3
+ public enum BiometryStrength {
4
+ WEAK,
5
+ STRONG,
6
+ }
@@ -2,8 +2,10 @@ import { WebPlugin } from '@capacitor/core';
2
2
  import type { PluginListenerHandle } from '@capacitor/core';
3
3
  import type { AuthenticateOptions, BiometricAuthPlugin, CheckBiometryResult, ResumeListener, BiometryType } from './definitions';
4
4
  export declare abstract class BiometricAuthBase extends WebPlugin implements BiometricAuthPlugin {
5
- abstract setBiometryType(type: BiometryType | string | undefined): Promise<void>;
5
+ abstract setBiometryType(type: BiometryType | string | Array<BiometryType | string> | undefined): Promise<void>;
6
6
  abstract checkBiometry(): Promise<CheckBiometryResult>;
7
+ abstract setBiometryIsEnrolled(enrolled: boolean): Promise<void>;
8
+ abstract setDeviceIsSecure(isSecure: boolean): Promise<void>;
7
9
  authenticate(options?: AuthenticateOptions): Promise<void>;
8
10
  protected abstract internalAuthenticate(options?: AuthenticateOptions): Promise<void>;
9
11
  addResumeListener(listener: ResumeListener): Promise<PluginListenerHandle> & PluginListenerHandle;
package/dist/esm/base.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { App } from '@capacitor/app';
2
2
  import { CapacitorException, WebPlugin } from '@capacitor/core';
3
3
  import { BiometryError } from './definitions';
4
- // eslint-disable-next-line import/prefer-default-export
5
4
  export class BiometricAuthBase extends WebPlugin {
6
5
  async authenticate(options) {
7
6
  try {