Skip to content

Native Login in SDK debug Login Options + AuthFlowTester Native Login UI tests - #4192

Merged
wmathurin merged 17 commits into
forcedotcom:devfrom
wmathurin:authflowtester-native-login
Oct 25, 2026
Merged

wmathurin merged 17 commits into
forcedotcom:devfrom
wmathurin:authflowtester-native-login

Conversation

@wmathurin

@wmathurin wmathurin commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Adds a developer-support Native Login mode to the SDK's Login Options, so any app with developer support enabled (the default in debug builds, for example RestAPIExplorer) can use Native Login without source changes, plus Native Login UI tests in AuthFlowTester. GUS: W-24600763. Android counterpart: forcedotcom/SalesforceMobileSDK-Android#3065. Builds on the Native Login DPoP work in #4187.

Screenshots

Native login form Login Options (Native) Login Options (Browser)
Native login form Login Options, Native selected Login Options, Browser selected

SDK (when developer support is enabled)

  • Authentication Mode (Browser / Native) at the top of Login Options. In Native mode: Client ID, Native Callback URL, Community Login URL, Use DPoP and Use Hybrid Authentication; browser-only sections are hidden. Native and browser configurations are saved separately, and closing Login Options restarts authentication after a change.
  • Built-in SwiftUI native login (username, password, loading, Cancel (with an account it returns to it; without one it invalidates the in-flight SDK attempt, so a token response arriving later creates no account and Log In works again), explicit result/error detail, "Log In with Browser" fallback). A native login view controller registered by the app via useNativeLogin always takes precedence.
  • SalesforceManager.shared.debugLoginSettings: apply JSON / reset, so automation can configure the mode deterministically.
  • Redacted diagnostics (NativeLoginDiagnostics, recorded when developer support is enabled and written to the SDK log through SFSDKCoreLogger): stage, HTTP status, OAuth error, whether a DPoP proof and nonce were present. No tokens, codes, proofs, nonces or passwords.
  • The feature follows the runtime isDevSupportEnabled flag, which defaults to on in debug builds and off in release builds unless the app enables it. Apps that don't enable it, and Browser mode, are unchanged.

AuthFlowTester

  • NativeLoginTests and NativeLoginPageObject: the four-case matrix (eca_jwt_headless and eca_jwt_dpop_headless, client DPoP off/on, with explicit Bearer / DPoP / rejected expectations), plus cancellation, browser fallback, same-user DPoP-to-Bearer reauthentication, scene lifecycle and diagnostics redaction. Successful sessions check feature markers, a REST call, revoke/refresh and a cold restart.
  • DEBUG launch arguments (--nativeLoginConfig, --authenticationMode) map onto the SDK debug settings.
  • ui_test_config.json.sample and the AuthFlowTester / RestAPIExplorer READMEs document setup, commands and missing-fixture behavior. docs/auth is updated.

For reviewers

  • New public surface (debug tooling): SalesforceManager.shared.debugLoginSettings.
  • Localization: new user-facing strings added to Localizable.strings: Login Options, the native login form, and the configuration/import error messages (NATIVE_LOGIN_DEBUG_ERROR_*).
  • Touches the login UI flow.

Testing

  • SDK unit tests: NativeLoginDebugSettingsTests (settings persistence and separation, routing and precedence, restart on change, Login Options visibility per mode, form state machine).
  • AuthFlowTester NativeLoginTests on iPhone 17 simulator against the test1 org: 12/12 pass (four-case matrix, retry after rejection, cancellation, no Cancel without an account, browser fallback, same-user DPoP-to-Bearer, backgrounding, redacted diagnostics, fixture validation).
  • Fixtures are looked up by name (eca_jwt_headless, eca_jwt_dpop_headless, community_auth host); the suite skips when neither ECA is present and fails when only one is.
  • The full AuthFlowTester UI test suite passes locally on the iPhone 17 simulator against the test1 org (new NativeLoginTests plus the existing browser, Community and DPoP suites).

…tomization hook

Superseded by SDK-level debug Native Login options in Login Options.
Debug apps (isDevSupportEnabled) can switch Login Options between Browser and
Native authentication. Native mode stores its own client ID, callback URL,
community URL, DPoP and hybrid choices (NativeLoginDebugSettings), apart from
the browser configuration, and presents the SDK's debug Native Login form.
A native login registered by the app with useNativeLogin always wins.

- NativeLoginDebugSettings / NativeLoginDebugConfig / AuthenticationMode,
  exposed as SalesforceManager.debugLoginSettings, with apply(json:) and
  reset(); reset is part of resetForUITesting
- AuthenticationModeView in Login Options, with JSON import
- DebugNativeLoginView(Controller) and NativeLoginDiagnostics (redacted
  per-attempt summary of the native login network calls)
- Routing applied at SDK init, on authenticate and when Login Options close;
  a mode or configuration change restarts authentication
- New Localizable strings for the mode picker, native fields and form
…in Options and form

Covers persistence apart from the browser config, JSON import, Login Options visibility, diagnostics redaction, the debug form state machine, routing precedence (app-registered native login wins) and restart on mode or configuration change.
…DK debug settings

--nativeLoginConfig=<json> and --authenticationMode=native|browser are applied through debugLoginSettings. NativeLoginTests drive the SDK debug form and Login Options import; the DPoP-required rejection now asserts HTTP 400 invalid_request (missing dpop_jkt).
docs/auth token lifecycle section, AuthFlowTester README and screenshots, and a RestAPIExplorer README explaining how to try Native Login from Login Options.
@github-actions

Copy link
Copy Markdown
1 Warning
⚠️ Big PR, try to keep changes smaller if you can.

Generated by 🚫 Danger

@github-actions

github-actions Bot commented Oct 22, 2026 •

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 2052 15
SFUserAccountManager Nullability Memory error Null passed to a callee that requires a non-null 2nd parameter 2067 15
SFUserAccountManager Nullability Memory error nil passed to a callee that requires a non-null 1st parameter 2796 21
SFUserAccountManager Nullability Memory error nil passed to a callee that requires a non-null 2nd parameter 3031 13
SalesforceSDKManager Nil value used as mutex for @synchronized() (no synchronization will occur) Logic error Nil value used as mutex for @synchronized() (no synchronization will occur) 171 5
SalesforceSDKManager Nil value used as mutex for @synchronized() (no synchronization will occur) Logic error Nil value used as mutex for @synchronized() (no synchronization will occur) 183 5

Generated by 🚫 Danger

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
TestsPassed ✅SkippedFailed
SalesforceSDKCore iOS ^18 Test Results1156 ran1156 ✅
TestResult
No test annotations available

@codecov

codecov Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.53147% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.60%. Comparing base (01930a9) to head (5d0bd60).
⚠️ Report is 7 commits behind head on dev.

Files with missing lines Patch % Lines
...asses/Login/DevConfig/AuthenticationModeView.swift 79.41% 21 Missing ⚠️
...ses/Login/DevConfig/NativeLoginDebugSettings.swift 92.92% 8 Missing ⚠️
...SDKCore/Classes/UserAccount/SFUserAccountManager.m 86.48% 5 Missing ⚠️
...forceSDKCore/Classes/Common/SalesforceSDKManager.m 95.34% 2 Missing ⚠️
...asses/Login/NativeLogin/DebugNativeLoginView.swift 99.33% 1 Missing ⚠️

❌ Your patch check has failed because the patch coverage (61.01%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #4192      +/-   ##
==========================================
- Coverage   72.92%   72.60%   -0.33%     
==========================================
  Files         254      258       +4     
  Lines       23531    24054     +523     
==========================================
+ Hits        17160    17464     +304     
- Misses       6371     6590     +219     
Components Coverage Δ
Analytics 70.78% <ø> (ø)
Common 71.57% <ø> (+0.37%) ⬆️
Core 69.04% <93.53%> (-0.39%) ⬇️
SmartStore 73.45% <ø> (ø)
MobileSync 88.99% <ø> (ø)
Files with missing lines Coverage Δ
...s/Login/DevConfig/LoginOptionsViewController.swift 98.87% <100.00%> (+0.06%) ⬆️
...ses/Login/NativeLogin/NativeLoginDiagnostics.swift 100.00% <100.00%> (ø)
...Login/NativeLogin/NativeLoginManagerInternal.swift 59.70% <100.00%> (+2.31%) ⬆️
...asses/Login/NativeLogin/DebugNativeLoginView.swift 99.33% <99.33%> (ø)
...forceSDKCore/Classes/Common/SalesforceSDKManager.m 78.71% <95.34%> (+0.97%) ⬆️
...SDKCore/Classes/UserAccount/SFUserAccountManager.m 71.70% <86.48%> (-1.38%) ⬇️
...ses/Login/DevConfig/NativeLoginDebugSettings.swift 92.92% <92.92%> (ø)
...asses/Login/DevConfig/AuthenticationModeView.swift 79.41% <79.41%> (ø)

... and 11 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

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
TestsPassed ☑️SkippedFailed ❌️
AuthFlowTester UI Test Results all148 ran50 ✅98 ❌
TestResult
AuthFlowTester UI Test Results all
AuthFlowTesterUITests.xctest
ForceAdvancedAuthTests.testForceAdvancedAuth_AddAdditionalUser_BackButtonAccessible()❌ failure
ForceAdvancedAuthTests.testForceAdvancedAuth_DefaultOn_LoginOptionsReachable()❌ failure
ForceAdvancedAuthTests.testForceAdvancedAuth_Disabled_StandardServer_UsesInAppWebView()❌ failure
ForceAdvancedAuthTests.testForceAdvancedAuth_Disabled_BackAndGearStillPresent()❌ failure
ForceAdvancedAuthTests.testForceAdvancedAuth_MyDomainRegularHost_RemainsBrowser()❌ failure
ForceAdvancedAuthTests.testForceAdvancedAuth_StandardServer_LaunchesExternalBrowser()❌ failure
RTRLoginTests.testECAJwtRtr_Hybrid()❌ failure
RTRLoginTests.testECAOpaqueRtr_Hybrid()❌ failure
RTRLoginTests.test_givenRTRRequestsInFlight_whenRevoked_thenBatchSettlesAndFollowUpSucceeds()❌ failure
RTRLoginTests.testECAJwtRtr_NoHybrid_WithRestart()❌ failure
RTRLoginTests.testECAJwtRtr_NoHybrid()❌ failure
RTRLoginTests.testECAOpaqueRtr_Hybrid_WithRestart()❌ failure
RTRLoginTests.testECAOpaqueRtr_NoHybrid_WithRestart()❌ failure
ConcurrentRestRequestTests.test_givenDeterministicRequestFailure_whenTappingRedSquare_thenDetailsAreShown()❌ failure
ConcurrentRestRequestTests.test_givenRTRRequestsInFlight_whenLoggingOut_thenColdRelaunchStaysLoggedOut()❌ failure
AdvancedAuthBeaconLoginTests.testBeaconJwt_AllScopes()❌ failure
AdvancedAuthBeaconLoginTests.testBeaconOpaque_SubsetScopes()❌ failure
AdvancedAuthBeaconLoginTests.testBeaconJwt_DefaultScopes()❌ failure
AdvancedAuthBeaconLoginTests.testBeaconOpaque_DefaultScopes()❌ failure
AdvancedAuthBeaconLoginTests.testBeaconOpaque_AllScopes()❌ failure
AdvancedAuthBeaconLoginTests.testBeaconJwt_SubsetScopes()❌ failure
LegacyLoginTests.testCAOpaque_AllScopes_WebServerFlow_InAppWebView()❌ failure
LegacyLoginTests.testCAOpaque_AllScopes_WebServerFlow()❌ failure
LegacyLoginTests.testCAOpaque_SubsetScopes_WebServerFlow_InAppWebView()❌ failure
LegacyLoginTests.testCAOpaque_SubsetScopes_WebServerFlow()❌ failure
LegacyLoginTests.testCAOpaque_DefaultScopes_WebServerFlow_InAppWebView()❌ failure
LegacyLoginTests.testCAOpaque_DefaultScopes_WebServerFlow()❌ failure
LegacyLoginTests.testCAOpaque_DefaultScopes_UserAgentFlow()❌ failure
LoginWithRestartTests.testMultiUserRestart()❌ failure
LoginWithRestartTests.testECAOpaque_DefaultScopes_WithRestart()❌ failure
LoginWithRestartTests.testBeaconJwt_DefaultScopes_DynamicConfiguration_WithRestart()❌ failure
LoginWithRestartTests.testAdvancedAuth_WithRestart()❌ failure
LoginWithRestartTests.testWelcomeDiscovery_WithRestart()❌ failure
LoginWithRestartTests.testBeaconOpaque_DefaultScopes_WithRestart()❌ failure
LoginForAdminTests.testLoginForAdmin_WebServerFlowEnabled()❌ failure
LoginForAdminTests.testLoginForAdmin_WebServerFlowDisabled()❌ failure
MultiUserLoginTests.testDifferentAppTypes_RevokeAccessForCaUser_EcaUserUnaffected()❌ failure
MultiUserLoginTests.test_givenRTRAndDPoPRTRUsers_whenCurrentUserLogsOutUnderLoad_thenOtherUserRemainsUsable()❌ failure
MultiUserLoginTests.testLogoutUserWithDynamicConfig_OtherUserUnaffected()❌ failure
MultiUserLoginTests.testFirstDynamic_SecondStatic_DifferentApps()❌ failure
MultiUserLoginTests.testDifferentAppTypes_LogoutCaUser_EcaUserUnaffected()❌ failure
MultiUserLoginTests.testBothStatic_SameApp_SameScopes()❌ failure
MultiUserLoginTests.testAdvancedAuthUser_HasBWFlag_RegularAuthUser_DoesNot()❌ failure
MultiUserLoginTests.testBothDynamic_DifferentApps()❌ failure
MultiUserLoginTests.testFlagDiversity_NonHybridOpaqueVsHybridJwt()❌ failure
MultiUserLoginTests.testFirstStatic_SecondDynamic_DifferentApps()❌ failure
MultiUserLoginTests.testBeaconAndNonBeacon_MultiUser()❌ failure
MultiUserLoginTests.testFlagDiversity_BeaconNonHybridJwtVsHybridOpaque()❌ failure
MultiUserLoginTests.testBothStatic_DifferentApps()❌ failure
MultiUserLoginTests.test_dpopAndNonDPoPUsers_flagOff_maintainIndependentProofs()❌ failure
MultiUserLoginTests.testBothStatic_SameApp_DifferentScopes()❌ failure
CommunityLoginTests.test_givenCommunityNoDPoPUser_whenAppRestart_thenSessionSurvives()❌ failure
CommunityLoginTests.test_givenCommunityDPoPHybrid_whenLogin_thenTokenTypeIsDPoPAndRefreshWorks()❌ failure
CommunityLoginTests.test_givenCommunityJwtNoDPoPNoHybrid_whenLogin_thenBearerAndRefreshWorks()❌ failure
CommunityLoginTests.test_givenCommunityDPoPSession_whenDowngradeFromDPoP_thenBearerUnbound()❌ failure
CommunityLoginTests.test_givenCommunityDPoPNoHybrid_whenLogin_thenTokenTypeIsDPoPAndRefreshWorks()❌ failure
CommunityLoginTests.test_givenCommunityBearerUser_whenLogoutAndReloginWithDPoP_thenTokenTypeIsDPoP()❌ failure
CommunityLoginTests.test_givenCommunityJwtNoDPoPHybrid_whenLogin_thenBearerAndRefreshWorks()❌ failure
CommunityLoginTests.test_givenCommunityDPoPUserAndRegularDPoPUser_whenSwitchAndRefresh_thenTokensAndNoncesAreIsolated()❌ failure
CommunityLoginTests.test_givenCommunityBearerSession_whenUpgradeToDPoP_thenDPoPBound()❌ failure
CommunityLoginTests.test_givenCommunityNoDPoP_whenLoginViaInAppWebView_thenTokenTypeIsBearer()❌ failure
DPoPLoginTests.test_givenDPoPSession_whenDowngradeFromDPoP_thenBearerUnbound()❌ failure
DPoPLoginTests.test_givenBearerSession_whenUpgradeToDPoP_thenDPoPBound()❌ failure
DPoPLoginTests.test_givenDPoPRtr_whenLoginViaPoolServer_thenRefreshTokenSurvivesIdentityFetch()❌ failure
DPoPLoginTests.test_givenTwoDPoPUsers_whenSwitchAndRefresh_thenTokensAndNoncesAreIsolated()❌ failure
DPoPLoginTests.test_givenDPoPRTRSessionWithInvalidAccessToken_whenMakingManyRequests_thenReplayPreservesBinding()❌ failure
DPoPLoginTests.test_givenDPoPRequestsInFlight_whenRevoked_thenBindingIsPreserved()❌ failure
DPoPLoginTests.test_givenDPoPHybrid_whenLogin_thenTokenTypeIsDPoPAndRefreshWorks()❌ failure
DPoPLoginTests.test_givenDPoPECA_whenAdminLogin_thenDPoPBindingWorksThroughBrowser()❌ failure
DPoPLoginTests.test_givenDPoPRtrNoHybrid_whenLogin_thenRefreshTokenRotatesAndDPoPBindingHolds()❌ failure
DPoPLoginTests.test_givenDPoPNoHybrid_whenLogin_thenTokenTypeIsDPoPAndRefreshWorks()❌ failure
DPoPLoginTests.test_givenDPoPUserWithSubsetScopes_whenMigrateToAllScopes_thenDPoPBindingPreserved()❌ failure
DPoPLoginTests.test_givenDPoPUser_whenMigrateToDPoPRtr_thenRefreshTokenRotationEnabled()❌ failure
RefreshTokenMigrationTests.testMigrateCAUserAgentToECAWebServer()❌ failure
RefreshTokenMigrationTests.testMigrateECA_AddMoreScopes()❌ failure
RefreshTokenMigrationTests.testMigrateCAToECA()❌ failure
RefreshTokenMigrationTests.testMigrateOneUserOnly()❌ failure
RefreshTokenMigrationTests.testMigrateCA_AddMoreScopes()❌ failure
RefreshTokenMigrationTests.testMigrateBeaconToCA()❌ failure
RefreshTokenMigrationTests.testMigrateBeacon_AddMoreScopes()❌ failure
RefreshTokenMigrationTests.testFlagDiversity_MigratedBeaconJwtVsNonHybridOpaque()❌ failure
RefreshTokenMigrationTests.testMigrateCAToBeaconAndBack()❌ failure
RefreshTokenMigrationTests.testMigrateCAUserAgentToBeaconWebServer()❌ failure
ECALoginTests.testECAJwt_SubsetScopes()❌ failure
ECALoginTests.testECAOpaque_AllScopes()❌ failure
ECALoginTests.testECAJwt_AllScopes()❌ failure
ECALoginTests.testDynamicConfigurationWithInvalidClientId()❌ failure
WelcomeLoginTests.testWelcomeDiscovery_RegularAuthLoginHost_DynamicConfig()❌ failure
WelcomeLoginTests.testWelcomeDiscovery_RegularAuthLoginHost()❌ failure
WelcomeLoginTests.testWelcomeDiscovery_AdvancedAuthLoginHost_DynamicConfig()❌ failure
LegacyLoginTestsNotHybrid.testCAOpaque_DefaultScopes_WebServerFlow()❌ failure
LegacyLoginTestsNotHybrid.testCAOpaque_SubsetScopes_WebServerFlow_InAppWebView()❌ failure
LegacyLoginTestsNotHybrid.testCAOpaque_AllScopes_WebServerFlow()❌ failure
LegacyLoginTestsNotHybrid.testCAOpaque_DefaultScopes_UserAgentFlow()❌ failure
LegacyLoginTestsNotHybrid.testCAOpaque_SubsetScopes_WebServerFlow()❌ failure
NativeLoginTests.test_givenSameUser_whenDPoPThenBearerReauth_thenBearerWithoutDPoPMarker()❌ failure
NativeLoginTests.test_givenRejection_whenRetryWithDPoP_thenSucceeds()❌ failure
RefreshTokenMigrationWithRestartTests.testMigrateMultipleUsers_WithRestart()❌ failure

@wmathurin

Copy link
Copy Markdown
Contributor Author

iPhone 17 simulator: Full AuthFlowTester UI suite run locally: all tests pass against the test1 org (new NativeLoginTests plus the existing browser, Community, DPoP, RTR and restart suites).

@JohnsonEricAtSalesforce JohnsonEricAtSalesforce left a comment

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.

Request changes on f82a52db01b432508c8ad6058db8f929f9fc7518.

Open Questions

  1. Should Native Login follow isDevSupportEnabled in Release, like the existing developer menu, or additionally require DEBUG? The PR says debug-only, but the API documents the runtime flag as its gate; that flag also works in Release.
  2. What explains the remaining CI failures? Core’s host-list restart test expects test.salesforce.com but gets l, reproduced locally with native registration left by test initialization. Two native UI tests fail to return from Login Options; that transition passed offline locally, so their CI cause remains unclear.
  3. Is there coverage for scene disconnect/replacement and multi-account isolation through the built-in form? The new UI tests exercise idle-form backgrounding/cancellation; prerequisite manager tests do not cover replacement of the debug form’s default registration.

Verification: focused Core 116/117 passed; two offline UI checks passed. The cancellation probe reproduced late account creation for Bearer and DPoP. All 12 live-org native tests skipped locally; the live matrix, full Core/browser suites and manual RestAPIExplorer login remain unverified. Core patch coverage reports 59.16% against an 80% target.

This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce.

/// account when there is one.
func cancel() {
stopLogin()
if canDismiss {

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.

[P1] Cancel the SDK attempt even without an existing account

Without an existing account, Cancel skips cancelAction and only cancels the Swift task. Cancellation does not propagate through the Native Login/REST bridge. Holding a successful token response until after Cancel still created and selected an account for both Bearer and DPoP. Invalidate the owning SDK attempt, keep retry working, and add a regression that releases token success after Cancel.

This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce.

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.

Fixed in a30fcff. Cancel now also invalidates the SDK attempt when there is no account (new cancelNativeAuthentications, which resets the in-flight native sessions without dismissing the login screen), so a token response released after Cancel is no longer the current attempt and creates no account; Log In starts a fresh attempt. Regression: test_givenDebugFormWithoutAccount_whenCancelledBeforeTokenSuccess_thenNoAccountAndRetryWorks holds the token response, cancels, releases it (Bearer and DPoP), asserts no account/identity request, then retries successfully. Plus unit tests for the new method (existing account and its DPoP key untouched, browser session kept).

/// The first problem with this configuration, or nil when Native Login can use it.
public var validationError: String? {
if consumerKey.trimmingCharacters(in: .whitespaces).isEmpty {
return "Client ID is required"

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.

[P2] Localize configuration errors

AuthenticationModeView displays these English validation messages and NativeLoginDebugSettingsError.errorDescription directly. Add localization keys for validation/import errors and resolve them through SFSDKResourceUtils.localizedString, including formatted messages.

This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce.

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.

Fixed in a30fcff. Validation and import errors now resolve through SFSDKResourceUtils.localizedString (NATIVE_LOGIN_DEBUG_ERROR_* keys, formatted messages use String(format:)). New strings are called out in the PR description.

func record(_ event: NativeLoginDiagnosticEvent) {
guard isEnabled() else { return }
lock.withLock { recorded.append(event) }
Self.logger.debug("\(event.summary, privacy: .public)")

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.

[P2] Use the SDK logger for diagnostics

os.Logger bypasses SDK log-level controls and the configurable SFLogger receiver. Send the redacted summary through SFSDKCoreLogger so apps receive and control these diagnostics through their existing logging setup.

This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce.

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.

Fixed in a30fcff. Diagnostics now go through SFSDKCoreLogger at debug level; os.Logger is removed.

…e debug errors; log diagnostics through SFSDKCoreLogger; fix host-list test isolation; gating docs
@wmathurin

Copy link
Copy Markdown
Contributor Author

Release gating: the feature follows the runtime isDevSupportEnabled flag (default on in debug builds, off in release unless the app enables it), on both platforms; PR description and docs now say so. Also fixed the Core unit test LoginForAdminTests host-list restart, which failed only in the full suite because nativeLoginEnabled was left on by other suites (it now isolates that state and takes down the auth window it presents). Full SalesforceSDKCore scheme run locally (without the credential-dependent SalesforceRestAPITests): all pass except SFUserAccountManagerTests.testLogin, which needs live org credentials.

@wmathurin

Copy link
Copy Markdown
Contributor Author

Re the two native UI tests that fail to return from Login Options in CI: they pass locally, and the full AuthFlowTester UI suite passes on the iPhone 17 simulator against the test1 org, so we are not investigating them further here. We will look at them if they keep failing on the next CI run. Scene disconnect/replacement is not covered: the test app has a single UIWindowScene.

@JohnsonEricAtSalesforce JohnsonEricAtSalesforce left a comment

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.

Follow-up at a30fcff6198a68d9f5f064f664d9e7076bb895d7: the original late-token cancellation case, localized errors and SDK logging are verified fixed. All 121 focused Core tests pass, as does the original cancellation reproduction. Runtime developer-support gating is clarified. Request changes for the remaining cancellation-isolation issue below.

Open Questions

  1. Current CI still fails native retry, logout/reauthentication and browser fallback. Retry/fallback snapshots have no login controls; logout retains session details. Offline restart/fallback pass locally. What explains these transitions? An unrelated CI UI timeout does not establish the same cause.
  2. Core patch coverage remains 60.32% against 80%. The check filters to SalesforceSDKCore, while UI uploads use AuthFlowTesterUI; the 93.42% combined headline does not clear that gate. Should the gate include UI coverage, or should unit coverage meet it separately? See Codecov flag filtering.

This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce.

dispatch_sync(dispatch_get_main_queue(), ^{ [self cancelNativeAuthentications]; });
return;
}
for (SFSDKAuthSession *session in self.authSessions.allValues) {

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.

[P2] Cancel only the form’s owning native attempt

DebugNativeLoginModel.stopLogin() calls this helper when one form is cancelled, but it resets every active native session. In a deterministic probe, cancelling the first form also removed a separately owned native session and deleted its DPoP key, preventing that login from completing. Scope invalidation to the cancelling form’s captured attempt/scene and add a two-session isolation regression.

This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce.

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.

Fixed. Cancel now invalidates only the attempt the form owns: cancelNativeAuthenticationsForConfiguration: matches an authenticating native session by scene, client ID, callback URL and login site, and resets just that session (its DPoP key and nonce are cleaned up with it). Other native sessions and browser sessions are left alone. Added a regression that runs a browser session and a second native session (different client and login URL) alongside the form's attempt: only the form's attempt is invalidated, and the other native session stays authenticating with its DPoP key. Focused NativeLoginManagerTests, DebugNativeLoginModelTests and LoginForAdminTests pass.

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.

Verified at 5d0bd60c: late token responses after Cancel no longer create accounts, and cancelling one form preserves other sessions in the regression tests. All 121 affected Core tests pass. This addresses my cancellation finding.

This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce.

@wmathurin

Copy link
Copy Markdown
Contributor Author

On the Core patch coverage question (60.32% vs 80%): that gate is filtered to the SalesforceSDKCore Codecov flag, so it only counts the SalesforceSDKCoreTests unit tests. The AuthFlowTester UI tests are not part of it; their coverage uploads under AuthFlowTesterUI. The 93.42% figure is the combined headline and does not clear the Core gate. I'm adding Core unit tests for the uncovered lines.

@wmathurin

Copy link
Copy Markdown
Contributor Author

On the CI UI test failures (native retry, logout/reauth and browser fallback): CI has been unreliable for the UI suites, and all of the AuthFlowTester UI tests, including these, pass locally. We'll rely on the CI re-run for the rest.

@JohnsonEricAtSalesforce JohnsonEricAtSalesforce left a comment

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.

Verified at 5d0bd60c: late token responses after Cancel no longer create accounts, and cancelling one form preserves other sessions in the regression tests. All 121 affected Core tests pass. This addresses my cancellation finding.

This response was generated by an AI agent on behalf of @JohnsonEricAtSalesforce.

@wmathurin
wmathurin merged commit 465f835 into forcedotcom:dev Oct 25, 2026
22 of 24 checks passed
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.

2 participants