Skip to content

Commit c0a8db1

Browse files
authored
[API breaking] Support localizedCancelTitle (iOS) and allowedAuthenticatorsOn{Enable,Authenticate} (Android) #392
ref DEV-3176 ref DEV-3177
2 parents dbd1147 + 2aba852 commit c0a8db1

File tree

11 files changed

+145
-54
lines changed

11 files changed

+145
-54
lines changed

example/capacitor/src/pages/Home.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import authgearCapacitor, {
4444
BiometricOptions,
4545
BiometricAccessConstraintIOS,
4646
BiometricLAPolicy,
47-
BiometricAccessConstraintAndroid,
47+
BiometricAuthenticatorAndroid,
4848
WebKitWebViewUIImplementation,
4949
DeviceBrowserUIImplementation,
5050
} from "@authgear/capacitor";
@@ -76,15 +76,22 @@ const REDIRECT_URI_CAPACITOR = "com.authgear.exampleapp.capacitor://host/path";
7676
const biometricOptions: BiometricOptions = {
7777
ios: {
7878
localizedReason: "Use biometric to authenticate",
79+
localizedCancelTitle: "Customized Cancel",
7980
constraint: BiometricAccessConstraintIOS.BiometryCurrentSet,
8081
policy: BiometricLAPolicy.deviceOwnerAuthenticationWithBiometrics,
8182
},
8283
android: {
8384
title: "Biometric Authentication",
8485
subtitle: "Biometric authentication",
8586
description: "Use biometric to authenticate",
86-
negativeButtonText: "Cancel",
87-
constraint: [BiometricAccessConstraintAndroid.BiometricStrong],
87+
negativeButtonText: "Customized Cancel",
88+
allowedAuthenticatorsOnEnable: [
89+
BiometricAuthenticatorAndroid.BiometricStrong,
90+
],
91+
allowedAuthenticatorsOnAuthenticate: [
92+
BiometricAuthenticatorAndroid.BiometricStrong,
93+
BiometricAuthenticatorAndroid.DeviceCredential,
94+
],
8895
invalidatedByBiometricEnrollment: true,
8996
},
9097
};

example/reactnative/src/screens/MainScreen.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import authgear, {
3030
BiometricOptions,
3131
BiometricAccessConstraintIOS,
3232
BiometricLAPolicy,
33-
BiometricAccessConstraintAndroid,
33+
BiometricAuthenticatorAndroid,
3434
SessionState,
3535
WebKitWebViewUIImplementation,
3636
} from '@authgear/react-native';
@@ -132,15 +132,22 @@ const wechatRedirectURI = Platform.select<string>({
132132
const biometricOptions: BiometricOptions = {
133133
ios: {
134134
localizedReason: 'Use biometric to authenticate',
135+
localizedCancelTitle: 'Customized Cancel',
135136
constraint: BiometricAccessConstraintIOS.BiometryCurrentSet,
136137
policy: BiometricLAPolicy.deviceOwnerAuthenticationWithBiometrics,
137138
},
138139
android: {
139140
title: 'Biometric Authentication',
140141
subtitle: 'Biometric authentication',
141142
description: 'Use biometric to authenticate',
142-
negativeButtonText: 'Cancel',
143-
constraint: [BiometricAccessConstraintAndroid.BiometricStrong],
143+
negativeButtonText: 'Customized Cancel',
144+
allowedAuthenticatorsOnEnable: [
145+
BiometricAuthenticatorAndroid.BiometricStrong,
146+
],
147+
allowedAuthenticatorsOnAuthenticate: [
148+
BiometricAuthenticatorAndroid.BiometricStrong,
149+
BiometricAuthenticatorAndroid.DeviceCredential,
150+
],
144151
invalidatedByBiometricEnrollment: true,
145152
},
146153
};

packages/authgear-capacitor/android/src/main/java/com/authgear/capacitor/Authgear.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -237,19 +237,19 @@ JSONObject getDeviceInfo(Context ctx) throws Exception {
237237
return rootMap;
238238
}
239239

240-
int checkBiometricSupported(Context ctx, int flags) throws Exception {
240+
int checkBiometricSupported(Context ctx, int allowedAuthenticatorsOnEnableFlags, int allowedAuthenticatorsOnAuthenticateFlags) throws Exception {
241241
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
242242
throw this.makeBiometricMinimumAPILevelException();
243243
}
244244

245245
BiometricManager manager = BiometricManager.from(ctx);
246-
int result = manager.canAuthenticate(flags);
246+
int result = manager.canAuthenticate(allowedAuthenticatorsOnEnableFlags);
247247

248248
if (result == BiometricManager.BIOMETRIC_SUCCESS) {
249249
// Further test if the key pair generator can be initialized.
250250
// https://issuetracker.google.com/issues/147374428#comment9
251251
try {
252-
this.createKeyPairGenerator(this.makeGenerateKeyPairSpec("__test__", true, flags, true));
252+
this.createKeyPairGenerator(this.makeGenerateKeyPairSpec("__test__", true, allowedAuthenticatorsOnAuthenticateFlags, true));
253253
} catch (Exception e) {
254254
// This branch is reachable only when there is a weak face and no strong fingerprints.
255255
// So we treat this situation as BIOMETRIC_ERROR_NONE_ENROLLED.
@@ -266,11 +266,11 @@ void createBiometricPrivateKey(AppCompatActivity activity, BiometricOptions opti
266266
throw this.makeBiometricMinimumAPILevelException();
267267
}
268268

269-
BiometricPrompt.PromptInfo promptInfo = this.buildPromptInfo(options);
269+
BiometricPrompt.PromptInfo promptInfo = this.buildPromptInfo(options, options.allowedAuthenticatorsOnEnableFlags);
270270
KeyGenParameterSpec spec = this.makeGenerateKeyPairSpec(
271271
options.alias,
272272
true,
273-
this.authenticatorTypesToKeyProperties(options.flags),
273+
this.authenticatorTypesToKeyProperties(options.allowedAuthenticatorsOnAuthenticateFlags),
274274
options.invalidatedByBiometricEnrollment
275275
);
276276
KeyPair keyPair = this.createKeyPair(spec);
@@ -289,7 +289,7 @@ void signWithBiometricPrivateKey(AppCompatActivity activity, BiometricOptions op
289289
throw this.makeBiometricMinimumAPILevelException();
290290
}
291291

292-
BiometricPrompt.PromptInfo promptInfo = this.buildPromptInfo(options);
292+
BiometricPrompt.PromptInfo promptInfo = this.buildPromptInfo(options, options.allowedAuthenticatorsOnAuthenticateFlags);
293293
KeyPair keyPair = this.getPrivateKey(options.alias);
294294
this.signBiometricJWT(
295295
activity,
@@ -435,14 +435,15 @@ private KeyPair getPrivateKey(String alias) throws Exception {
435435
}
436436

437437
private BiometricPrompt.PromptInfo buildPromptInfo(
438-
BiometricOptions options
438+
BiometricOptions options,
439+
int flags
439440
) {
440441
BiometricPrompt.PromptInfo.Builder builder = new BiometricPrompt.PromptInfo.Builder();
441442
builder.setTitle(options.title);
442443
builder.setSubtitle(options.subtitle);
443444
builder.setDescription(options.description);
444-
builder.setAllowedAuthenticators(options.flags);
445-
if ((options.flags & BiometricManager.Authenticators.DEVICE_CREDENTIAL) == 0) {
445+
builder.setAllowedAuthenticators(flags);
446+
if ((flags & BiometricManager.Authenticators.DEVICE_CREDENTIAL) == 0) {
446447
builder.setNegativeButtonText(options.negativeButtonText);
447448
}
448449
return builder.build();

packages/authgear-capacitor/android/src/main/java/com/authgear/capacitor/AuthgearPlugin.java

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,14 @@ private void handleOpenAuthorizeURLWithWebView(PluginCall call, ActivityResult a
189189
@PluginMethod
190190
public void checkBiometricSupported(PluginCall call) {
191191
JSObject android = call.getObject("android");
192-
JSONArray constraint = this.jsObjectGetArray(android, "constraint");
193-
int flags = this.constraintToFlag(constraint);
192+
JSONArray allowedAuthenticatorsOnEnable = this.jsObjectGetArray(android, "allowedAuthenticatorsOnEnable");
193+
JSONArray allowedAuthenticatorsOnAuthenticate = this.jsObjectGetArray(android, "allowedAuthenticatorsOnAuthenticate");
194+
int allowedAuthenticatorsOnEnableFlags = this.constraintToFlag(allowedAuthenticatorsOnEnable);
195+
int allowedAuthenticatorsOnAuthenticateFlags = this.constraintToFlag(allowedAuthenticatorsOnAuthenticate);
194196

195197
Context ctx = this.getContext();
196198
try {
197-
int result = this.implementation.checkBiometricSupported(ctx, flags);
199+
int result = this.implementation.checkBiometricSupported(ctx, allowedAuthenticatorsOnEnableFlags, allowedAuthenticatorsOnAuthenticateFlags);
198200
if (result == BiometricManager.BIOMETRIC_SUCCESS) {
199201
call.resolve();
200202
} else {
@@ -214,19 +216,23 @@ public void createBiometricPrivateKey(PluginCall call) {
214216
String kid = call.getString("kid");
215217
String alias = "com.authgear.keys.biometric." + kid;
216218
JSObject android = call.getObject("android");
217-
JSONArray constraint = this.jsObjectGetArray(android, "constraint");
218219
boolean invalidatedByBiometricEnrollment = android.getBool("invalidatedByBiometricEnrollment");
219-
int flags = this.constraintToFlag(constraint);
220220
String title = android.getString("title");
221221
String subtitle = android.getString("subtitle");
222222
String description = android.getString("description");
223223
String negativeButtonText = android.getString("negativeButtonText");
224224

225+
JSONArray allowedAuthenticatorsOnEnable = this.jsObjectGetArray(android, "allowedAuthenticatorsOnEnable");
226+
JSONArray allowedAuthenticatorsOnAuthenticate = this.jsObjectGetArray(android, "allowedAuthenticatorsOnAuthenticate");
227+
int allowedAuthenticatorsOnEnableFlags = this.constraintToFlag(allowedAuthenticatorsOnEnable);
228+
int allowedAuthenticatorsOnAuthenticateFlags = this.constraintToFlag(allowedAuthenticatorsOnAuthenticate);
229+
225230
BiometricOptions options = new BiometricOptions();
226231
options.payload = payload;
227232
options.kid = kid;
228233
options.alias = alias;
229-
options.flags = flags;
234+
options.allowedAuthenticatorsOnEnableFlags = allowedAuthenticatorsOnEnableFlags;
235+
options.allowedAuthenticatorsOnAuthenticateFlags = allowedAuthenticatorsOnAuthenticateFlags;
230236
options.invalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
231237
options.title = title;
232238
options.subtitle = subtitle;
@@ -269,19 +275,23 @@ public void signWithBiometricPrivateKey(PluginCall call) {
269275
String kid = call.getString("kid");
270276
String alias = "com.authgear.keys.biometric." + kid;
271277
JSObject android = call.getObject("android");
272-
JSONArray constraint = this.jsObjectGetArray(android, "constraint");
273278
boolean invalidatedByBiometricEnrollment = android.getBool("invalidatedByBiometricEnrollment");
274-
int flags = this.constraintToFlag(constraint);
275279
String title = android.getString("title");
276280
String subtitle = android.getString("subtitle");
277281
String description = android.getString("description");
278282
String negativeButtonText = android.getString("negativeButtonText");
279283

284+
JSONArray allowedAuthenticatorsOnEnable = this.jsObjectGetArray(android, "allowedAuthenticatorsOnEnable");
285+
JSONArray allowedAuthenticatorsOnAuthenticate = this.jsObjectGetArray(android, "allowedAuthenticatorsOnAuthenticate");
286+
int allowedAuthenticatorsOnEnableFlags = this.constraintToFlag(allowedAuthenticatorsOnEnable);
287+
int allowedAuthenticatorsOnAuthenticateFlags = this.constraintToFlag(allowedAuthenticatorsOnAuthenticate);
288+
280289
BiometricOptions options = new BiometricOptions();
281290
options.payload = payload;
282291
options.kid = kid;
283292
options.alias = alias;
284-
options.flags = flags;
293+
options.allowedAuthenticatorsOnEnableFlags = allowedAuthenticatorsOnEnableFlags;
294+
options.allowedAuthenticatorsOnAuthenticateFlags = allowedAuthenticatorsOnAuthenticateFlags;
285295
options.invalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
286296
options.title = title;
287297
options.subtitle = subtitle;

packages/authgear-capacitor/android/src/main/java/com/authgear/capacitor/BiometricOptions.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ class BiometricOptions {
66
JSObject payload;
77
String kid;
88
String alias;
9-
int flags;
9+
int allowedAuthenticatorsOnEnableFlags;
10+
int allowedAuthenticatorsOnAuthenticateFlags;
1011
boolean invalidatedByBiometricEnrollment;
1112
String title;
1213
String subtitle;

packages/authgear-capacitor/ios/Plugin/AuthgearPlugin.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,10 @@ public class AuthgearPlugin: CAPPlugin {
136136
@objc func checkBiometricSupported(_ call: CAPPluginCall) {
137137
let ios = call.getObject("ios")!
138138
let policyString = ios["policy"] as! String
139+
let localizedCancelTitle = ios["localizedCancelTitle"] as? String
139140
DispatchQueue.main.async {
140141
do {
141-
try self.impl.checkBiometricSupported(policyString: policyString)
142+
try self.impl.checkBiometricSupported(policyString: policyString, localizedCancelTitle: localizedCancelTitle)
142143
call.resolve()
143144
} catch {
144145
error.reject(call)
@@ -153,12 +154,14 @@ public class AuthgearPlugin: CAPPlugin {
153154
let constraint = ios["constraint"] as! String
154155
let policyString = ios["policy"] as! String
155156
let localizedReason = ios["localizedReason"] as! String
157+
let localizedCancelTitle = ios["localizedCancelTitle"] as? String
156158
let tag = "com.authgear.keys.biometric.\(kid)"
157159

158160
DispatchQueue.main.async {
159161
self.impl.createBiometricPrivateKey(
160162
policyString: policyString,
161163
localizedReason: localizedReason,
164+
localizedCancelTitle: localizedCancelTitle,
162165
constraint: constraint,
163166
kid: kid,
164167
tag: tag,
@@ -182,12 +185,14 @@ public class AuthgearPlugin: CAPPlugin {
182185
let ios = call.getObject("ios")!
183186
let policyString = ios["policy"] as! String
184187
let localizedReason = ios["localizedReason"] as! String
188+
let localizedCancelTitle = ios["localizedCancelTitle"] as? String
185189
let tag = "com.authgear.keys.biometric.\(kid)"
186190

187191
DispatchQueue.main.async {
188192
self.impl.signWithBiometricPrivateKey(
189193
policyString: policyString,
190194
localizedReason: localizedReason,
195+
localizedCancelTitle: localizedCancelTitle,
191196
kid: kid,
192197
tag: tag,
193198
payload: payload

packages/authgear-capacitor/ios/Plugin/AuthgearPluginImpl.swift

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,10 @@ import Capacitor
250250
controller?.start()
251251
}
252252

253-
@objc func checkBiometricSupported(policyString: String) throws {
253+
@objc func checkBiometricSupported(policyString: String, localizedCancelTitle: String?) throws {
254254
if #available(iOS 11.3, *) {
255255
let policy = LAPolicy.from(string: policyString)!
256-
let laContext = self.makeLAContext(policy: policy)
256+
let laContext = self.makeLAContext(policy: policy, localizedCancelTitle: localizedCancelTitle)
257257
var error: NSError?
258258
laContext.canEvaluatePolicy(policy, error: &error)
259259
if let error = error {
@@ -267,14 +267,15 @@ import Capacitor
267267
@objc func createBiometricPrivateKey(
268268
policyString: String,
269269
localizedReason: String,
270+
localizedCancelTitle: String?,
270271
constraint: String,
271272
kid: String,
272273
tag: String,
273274
payload: [String: Any],
274275
completion: @escaping (String?, Error?) -> Void
275276
) {
276277
let policy = LAPolicy.from(string: policyString)!
277-
let ctx = makeLAContext(policy: policy)
278+
let ctx = makeLAContext(policy: policy, localizedCancelTitle: localizedCancelTitle)
278279
ctx.evaluatePolicy(policy, localizedReason: localizedReason) { ok, error in
279280
if let error = error {
280281
completion(nil, error)
@@ -296,13 +297,14 @@ import Capacitor
296297
@objc func signWithBiometricPrivateKey(
297298
policyString: String,
298299
localizedReason: String,
300+
localizedCancelTitle: String?,
299301
kid: String,
300302
tag: String,
301303
payload: [String: Any],
302304
completion: @escaping (String?, Error?) -> Void
303305
) {
304306
let policy = LAPolicy.from(string: policyString)!
305-
let ctx = makeLAContext(policy: policy)
307+
let ctx = makeLAContext(policy: policy, localizedCancelTitle: localizedCancelTitle)
306308
ctx.evaluatePolicy(policy, localizedReason: localizedReason) { ok, error in
307309
if let error = error {
308310
completion(nil, error)
@@ -393,11 +395,12 @@ import Capacitor
393395
return window
394396
}
395397

396-
private func makeLAContext(policy: LAPolicy) -> LAContext {
398+
private func makeLAContext(policy: LAPolicy, localizedCancelTitle: String?) -> LAContext {
397399
let ctx = LAContext()
398400
if policy == LAPolicy.deviceOwnerAuthenticationWithBiometrics {
399401
ctx.localizedFallbackTitle = "";
400402
}
403+
ctx.localizedCancelTitle = localizedCancelTitle
401404
return ctx
402405
}
403406

packages/authgear-capacitor/src/types.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,17 @@ export enum BiometricAccessConstraintIOS {
277277
*/
278278
export interface BiometricOptionsIOS {
279279
/**
280-
* See https://developer.apple.com/documentation/localauthentication/lacontext/1514176-evaluatepolicy#parameters
280+
* See https://developer.apple.com/documentation/localauthentication/lacontext/localizedreason
281281
*
282282
* @public
283283
*/
284284
localizedReason: string;
285+
/**
286+
* See https://developer.apple.com/documentation/localauthentication/lacontext/localizedcanceltitle
287+
*
288+
* @public
289+
*/
290+
localizedCancelTitle?: string;
285291
constraint: BiometricAccessConstraintIOS;
286292
policy: BiometricLAPolicy;
287293
}
@@ -291,7 +297,7 @@ export interface BiometricOptionsIOS {
291297
*
292298
* @public
293299
*/
294-
export enum BiometricAccessConstraintAndroid {
300+
export enum BiometricAuthenticatorAndroid {
295301
/**
296302
* The user can use Class 3 biometric to authenticate.
297303
*
@@ -340,7 +346,28 @@ export interface BiometricOptionsAndroid {
340346
* @public
341347
*/
342348
negativeButtonText: string;
343-
constraint: BiometricAccessConstraintAndroid[];
349+
/**
350+
* Set the allowed authenticators when the user enables biometric.
351+
* This must be a subset of allowedAuthenticatorsOnAuthenticate because
352+
* you normally want to ensure the user performed at least once biometric authentication during setup,
353+
* but allow the user to fallback to passcode for subsequent biometric authentication.
354+
*
355+
* See {@link BiometricAuthenticatorAndroid}
356+
*
357+
* @public
358+
*/
359+
allowedAuthenticatorsOnEnable: BiometricAuthenticatorAndroid[];
360+
/**
361+
* Set the allowed authenticators when the user performs biometric authentication.
362+
* This must be a superset of allowedAuthenticatorsOnEnable because
363+
* you normally want to ensure the user performed at least once biometric authentication during setup,
364+
* but allow the user to fallback to passcode for subsequent biometric authentication.
365+
*
366+
* See {@link BiometricAuthenticatorAndroid}
367+
*
368+
* @public
369+
*/
370+
allowedAuthenticatorsOnAuthenticate: BiometricAuthenticatorAndroid[];
344371
/**
345372
* The user needs to set up biometric again when a new biometric is enrolled or all enrolled biometrics are removed.
346373
*

0 commit comments

Comments
 (0)