Skip to content

Add option to automatically present biometric opt-in and lock #4041

Merged
sfdctaka merged 5 commits into
forcedotcom:devfrom
sfdctaka:biometricOption
May 27, 2026
Merged

Add option to automatically present biometric opt-in and lock #4041
sfdctaka merged 5 commits into
forcedotcom:devfrom
sfdctaka:biometricOption

Conversation

@sfdctaka
Copy link
Copy Markdown
Contributor

Summary

  • Add automaticPresentation property to BiometricAuthenticationManager protocol that, when enabled:
    • Automatically presents the biometric opt-in dialog after login (if the user hasn't opted in yet)
    • Automatically triggers biometric unlock (Face ID/Touch ID) when the app locks (if the user has opted in)
  • Defaults to false to preserve existing behavior — apps must explicitly opt in
  • Ported @bbirman's changes

Changes

  • BiometricAuthenticationManager.swift: Added automaticPresentation property to the public protocol
  • BiometricAuthenticationManagerInternal.swift: Implemented stored property (default false) and added auto-present logic at the end of lock() that iterates connected scenes and calls presentBiometric(scene:) when conditions are met
  • SFUserAccountManager.m: After storing biometric policy on login, checks if opt-in dialog should be auto-presented (!hasBiometricOptedIn && automaticPresentation)
  • BiometricAuthenticationManagerTests.swift: Added 7 tests covering all combinations of automaticPresentation × hasBiometricOptedIn for both lock and opt-in dialog scenarios
  • URLSessionTask+RetryPolicyTests.swift: Added automaticPresentation to mock for protocol conformance

Test Matrix

Scenario A: Auto opt-in dialog after login

# automaticPresentation hasBiometricOptedIn Auth type Expected
A1 true false Fresh login Opt-in dialog presented
A2 true true Fresh login No dialog (already opted in)
A3 false false Fresh login No dialog
A4 true false Token refresh No dialog (only on fresh login)

Scenario B: Auto biometric prompt on lock

# automaticPresentation hasBiometricOptedIn Lock triggered Expected
B1 true true Yes presentBiometric called for each scene
B2 true false Yes No auto-present (not opted in)
B3 false true Yes No auto-present (feature off)
B4 false false Yes No auto-present

Scenario C: Interaction with native login

# Login type automaticPresentation Action Expected
C1 Native login true Lock then auto biometric presentBiometric works correctly (auth window dismisses, native login VC doesn't interfere)
C2 Native login true Fresh login then opt-in dialog Dialog presented on correct VC (mainWindow, not native login VC)
C3 Webview login true Lock then auto biometric Works as before
C4 Native login true Lock then biometric fails then fallback User falls back to native login screen correctly

Scenario D: Existing behavior (regression tests)

# Scenario Expected
D1 automaticPresentation = false (default) All existing behavior unchanged
D2 Lock/unlock without automaticPresentation User must manually tap biometric button
D3 Opt-in without automaticPresentation App must explicitly call presentOptInDialog

Test plan

  • Unit tests: Scenarios A1–A3, B1–B4, D1–D3 (7 new tests in BiometricAuthenticationManagerTests)
  • Manual test (C3): Standard template app with ECA + biometric custom attributes → opt-in dialog appears after first login
  • Manual test (B1): After opting in, background app past timeout → Face ID auto-presents on foreground
  • Manual test (D1): automaticPresentation = false (default) → no change in existing behavior
  • Manual test (C1–C4): Native login template + configured org with customer attributes: ENABLE_BIOMETRIC_AUTHENTICATION and BIOMETRIC_AUTHENTICATION_TIMEOUT

Resolves W-21981313

@github-actions
Copy link
Copy Markdown

1 Warning
⚠️ Static Analysis found an issue with one or more files you modified. Please fix the issue(s).

Clang Static Analysis Issues

File Type Category Description Line Col
SFUserAccountManager Nullability Memory error Null passed to a callee that requires a non-null 2nd parameter 1579 15
SFUserAccountManager Nullability Memory error Null passed to a callee that requires a non-null 2nd parameter 1594 15
SFUserAccountManager Nullability Memory error nil passed to a callee that requires a non-null 2nd parameter 2232 13

Generated by 🚫 Danger

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 21, 2026

TestsPassed ☑️SkippedFailed ❌️
SalesforceSDKCore iOS ^18 Test Results639 ran635 ✅4 ❌
TestResult
SalesforceSDKCore iOS ^18 Test Results
SFSDKAuthConfigUtilTests.testBrowserBasedLoginEnabled❌ failure
SalesforceRestAPITests.testCreateQuerySearchDelete❌ failure
SalesforceRestAPITests.testCreateUpdateQuerySearchDelete❌ failure
SalesforceRestAPITests.testUploadOwnedFilesDelete❌ failure

@codecov
Copy link
Copy Markdown

codecov Bot commented May 21, 2026

Codecov Report

❌ Patch coverage is 66.66667% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.24%. Comparing base (6c4b0f2) to head (00153e3).
⚠️ Report is 7 commits behind head on dev.

Files with missing lines Patch % Lines
...SDKCore/Classes/UserAccount/SFUserAccountManager.m 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #4041      +/-   ##
==========================================
+ Coverage   65.73%   68.24%   +2.51%     
==========================================
  Files         245      245              
  Lines       21458    21468      +10     
==========================================
+ Hits        14105    14651     +546     
+ Misses       7353     6817     -536     
Components Coverage Δ
Analytics 70.78% <ø> (ø)
Common 71.25% <ø> (ø)
Core 61.69% <81.81%> (+3.90%) ⬆️
SmartStore 73.44% <ø> (ø)
MobileSync 88.79% <ø> (ø)
Files with missing lines Coverage Δ
...ation/BiometricAuthenticationManagerInternal.swift 69.01% <100.00%> (+4.52%) ⬆️
...SDKCore/Classes/UserAccount/SFUserAccountManager.m 54.78% <0.00%> (+10.76%) ⬆️

... and 21 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 21, 2026

TestsPassed ☑️SkippedFailed ❌️
SalesforceSDKCore iOS ^26 Test Results639 ran636 ✅3 ❌
TestResult
SalesforceSDKCore iOS ^26 Test Results
testMissingLoginHint()❌ failure
SFSDKAuthConfigUtilTests.testBrowserBasedLoginEnabled❌ failure
SalesforceRestAPITests.testCreateQuerySearchDelete❌ failure

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 21, 2026

TestsPassed ✅SkippedFailed
AuthFlowTester UI Test Results all1 ran1 ✅
TestResult
No test annotations available

@sfdctaka sfdctaka marked this pull request as ready for review May 21, 2026 18:10
@sfdctaka sfdctaka requested review from bbirman and wmathurin May 21, 2026 18:11
@wmathurin wmathurin requested a review from brandonpage May 21, 2026 21:45
Copy link
Copy Markdown
Contributor

@wmathurin wmathurin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@bbirman
Copy link
Copy Markdown
Member

bbirman commented May 21, 2026

Just double checking, there wasn't any issue with having the new logic on top of the logic here in SFLoginViewController? Do they both get called but one ends up being a no-op?

@sfdctaka
Copy link
Copy Markdown
Contributor Author

@bbirman good catch. Both can fire:

The new logic in lock() fires first (synchronously after lock), before SFLoginViewController is presented (since login is async).
The existing logic in SFLoginViewController.viewDidLoad fires once the login VC loads.
presentBiometric(scene:) creates a fresh LAContext each time, so if the first call already resolved (or is in-progress), the second should be a no-op from the user's perspective — the system won't show two biometric prompts.

That said, the existing logic at line 95 (bioAuthManager.locked && bioAuthManager.hasBiometricOptedIn) already auto-presents biometric without checking automaticPresentation. So the existing behavior in SFLoginViewController was already doing automatic presentation for the standard login flow — the new code only adds it for the native login path (where SFLoginViewController isn't used). Should we gate the existing line 95 behind automaticPresentation as well for consistency?

@bbirman
Copy link
Copy Markdown
Member

bbirman commented May 22, 2026

@bbirman good catch. Both can fire:

The new logic in lock() fires first (synchronously after lock), before SFLoginViewController is presented (since login is async). The existing logic in SFLoginViewController.viewDidLoad fires once the login VC loads. presentBiometric(scene:) creates a fresh LAContext each time, so if the first call already resolved (or is in-progress), the second should be a no-op from the user's perspective — the system won't show two biometric prompts.

That said, the existing logic at line 95 (bioAuthManager.locked && bioAuthManager.hasBiometricOptedIn) already auto-presents biometric without checking automaticPresentation. So the existing behavior in SFLoginViewController was already doing automatic presentation for the standard login flow — the new code only adds it for the native login path (where SFLoginViewController isn't used). Should we gate the existing line 95 behind automaticPresentation as well for consistency?

That sounds good! The only downside is that whatever the default is would be a change for one of the flows but 14.0 would be a good window for it, @brandonpage @wmathurin any thoughts on making automatic presentation default to true so there's less enablement for biometric by default?

@sfdctaka
Copy link
Copy Markdown
Contributor Author

sfdctaka commented May 26, 2026

@bbirman @wmathurin @brandonpage Is it okay to automatically present the biometric the first time?

@brandonpage
Copy link
Copy Markdown
Contributor

@bbirman @wmathurin @brandonpage Is it okay to automatically present the biometric the first time?

Yes, and I would honestly prefer if it was opt-out. That was my original intention, but the client who we built this for (and never used it) wanted to show their own dialog. UI styling are very consistent on iOS so I really don't see an issue with showing our prompt. Perhaps on Android we may want to allow the app to override the dialog.

Comment on lines +151 to +156

if hasBiometricOptedIn() && automaticPresentation {
SFApplicationHelper.sharedApplication()?.connectedScenes.forEach() { scene in
presentBiometric(scene: scene)
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? The Login view controller should automatically present biometric upon lock if the user is opted in?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

This is for the native login path. When useNativeLogin is enabled, SFLoginViewController isn't used — the app's custom VC is presented instead, so the existing auto-present at line 95 of SFLoginViewController.m never fires. This ensures native login apps get the same auto-present behavior when automaticPresentation is enabled.

That said, for the standard (webview) login path, you're right — SFLoginViewController already handles it. Should we gate line 95 behind automaticPresentation too for consistency, or leave it as-is since it's existing behavior?

@sfdctaka sfdctaka merged commit d73eb2a into forcedotcom:dev May 27, 2026
23 of 30 checks passed
@sfdctaka sfdctaka deleted the biometricOption branch May 27, 2026 00:18
@bbirman
Copy link
Copy Markdown
Member

bbirman commented May 27, 2026

Should the code in SFLoginViewController be removed now that there's presentation logic for both paths in BiometricAuthenticationManagerInternal?

@sfdctaka
Copy link
Copy Markdown
Contributor Author

Should the code in SFLoginViewController be removed now that there's presentation logic for both paths in BiometricAuthenticationManagerInternal?

@bbirman The SFLoginViewController code predates this and doesn't check automaticPresentation, so they're not fully equivalent. Consolidating them would be a good follow-up (we'd want SFLoginViewController to respect automaticPresentation too), but it's a behavioral change for existing webview-login apps that should be its own PR. Want me to file a story for it?

@bbirman
Copy link
Copy Markdown
Member

bbirman commented May 27, 2026

Yes, that was the gap identified from the original proof of concept work on the internal branch, now that "automaticPresentation" defaults to true there shouldn't be a behavioral difference to the consuming app/user

@sfdctaka
Copy link
Copy Markdown
Contributor Author

@bbirman I'll push another PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants