Skip to content

Feature/support microsoft entra mfa#27

Merged
erikdarlingdata merged 10 commits intoerikdarlingdata:devfrom
ClaudioESSilva:feature/support-Microsoft-Entra-MFA
Feb 13, 2026
Merged

Feature/support microsoft entra mfa#27
erikdarlingdata merged 10 commits intoerikdarlingdata:devfrom
ClaudioESSilva:feature/support-Microsoft-Entra-MFA

Conversation

@ClaudioESSilva
Copy link
Contributor

What does this PR do?

Implements #20

Which component(s) does this affect?

  • Full Dashboard
  • Lite
  • SQL collection scripts
  • Installer
  • Documentation

How was this tested?

Tested against an Azure SQL DB

Screenshots

New option

image

With "Test Connection"

image

Changes made

Changes Made:

  1. UI Updates (AddServerDialog.xaml)
  • Added "Microsoft Entra MFA" radio button alongside Windows and SQL Server authentication
  1. Model Updates (ServerConnection.cs)
  • Added AuthenticationType property with values: "Windows", "SqlServer", or "EntraMFA"
  • Updated AuthenticationDisplay to show the friendly name for each auth type
  • Modified BuildConnectionString() to use SqlAuthenticationMethod.ActiveDirectoryInteractive for MFA
  • This triggers the Azure AD/Entra ID interactive authentication popup with MFA support
  1. Dialog Code-Behind (AddServerDialog.xaml.cs)
  • Updated to handle all three authentication modes
  • Shows appropriate UI panels based on selected authentication type
  • Test Connection button now triggers the MFA popup when testing Entra MFA connections
  • Loads and saves account hints for MFA connections
  1. Server Management (ServerManager.cs)
  • Updated credential storage logic to handle all auth types
  • For MFA: stores username as account hint (used to pre-populate the auth dialog)
  • For SQL Server: stores username and password
  • For Windows: no credentials stored

How It Works:

When you select Microsoft Entra MFA and connect to an Azure SQL Database:

  • The connection string uses SqlAuthenticationMethod.ActiveDirectoryInteractive
  • Microsoft.Data.SqlClient will automatically show the Azure AD/Entra ID authentication popup
  • Users complete MFA in the popup window (password + code, biometrics, etc.)
  • The optional account hint pre-fills the username in the dialog to speed up authentication
  • Azure AD/Entra ID handles all the MFA logic - no additional packages needed!

Challenges

Passed

  • When the app starts, it tries to connect to the instance, which leads to a popup of the MFA window. Because there are 16 queies to run, if we cancel the MFA, a 2nd one appears and we cancel and a 3rd and so on until we cancel 16 times. This isn't cool so I have requested (🤖) an implementation that when we cancel it, it won't pop up the other ones. Then if we hit "Connect", it will popup again.

Good as it is, or do we want to change behaviour?

  • If you have multiple Azure SQL DB connections, there isn't a way to tell the MFA belongs to which.
  • One option is: cancel all the MFA popups and then, connect to each one. Because you initiate that "Connect" action you know which MFA you need to select.
  • Another option would be to start the app with all Azure SQL DB connections disabled and the user need to explicitly "Connect".

@erikdarlingdata If as it is, if fine for a v1, let's move on and open a discussion. If you already have strong opinions on this and want to implement right away let me know what they are to try to meet them.

Checklist

  • I have read the contributing guide
  • My code builds with zero warnings (dotnet build -c Debug)
  • I have tested my changes against at least one SQL Server version
  • I have not introduced any hardcoded credentials or server names

Copy link
Owner

@erikdarlingdata erikdarlingdata left a comment

Choose a reason for hiding this comment

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

PR Review: Microsoft Entra MFA Support

Overall well-structured feature with good UX thinking around the MFA popup problem. The UserCancelledMfa flag and serialization via s_mfaAuthLock are solid approaches. A few issues to address, one of which is a breaking change for existing users.


Critical: Backward compatibility break

ServerConnection.AuthenticationType defaults to "Windows". Existing users with SQL Server auth have UseWindowsAuth = false in their servers.json but no AuthenticationType field. On deserialization, AuthenticationType will be "Windows" while UseWindowsAuth is false.

Since BuildConnectionString and GetConnectionString now branch on AuthenticationType instead of UseWindowsAuth, existing SQL auth connections will silently switch to Windows auth and break.

Needs migration logic in LoadServers() or a computed getter:

public string AuthenticationType 
{ 
    get => _authenticationType ?? (UseWindowsAuth ? "Windows" : "SqlServer");
    set => _authenticationType = value;
}

Bug: AADSTS50126 is not cancellation

IsMfaCancelledException includes aadsts50126 ("Error validating credentials due to invalid username or password"). This is wrong credentials, not user cancellation. A user who types the wrong password would have their server silently disabled until manual reconnect. This error code should be removed from the check.

AADSTS50058 ("Need to select account") is also debatable — it fires when multi-account selection is needed, not necessarily when the user cancels.

Code duplication: IsMfaCancelledException appears 3 times

Identical method in RemoteCollectorService.cs, ServerManager.cs, and AddServerDialog.xaml.cs. Should be extracted to a shared static helper (e.g., on ServerConnection or a small utility class).

Magic strings

"Windows", "SqlServer", "EntraMFA" are scattered across 7 files as raw string comparisons. One typo and things silently break. Consider constants or an enum:

public static class AuthTypes
{
    public const string Windows = "Windows";
    public const string SqlServer = "SqlServer";
    public const string EntraMFA = "EntraMFA";
}

Dead code in CheckConnectionAsync

Two sequential checks (around lines 355-382 in the diff):

// First check — returns for ALL MFA servers when !allowInteractiveAuth
if (!allowInteractiveAuth && server.AuthenticationType == "EntraMFA") { return...; }

// Second check — unreachable, first block already returned
if (server.AuthenticationType == "EntraMFA" && previousStatus.UserCancelledMfa && !allowInteractiveAuth) { return...; }

The second block is unreachable because the first already returns for all MFA servers during background checks.

UseWindowsAuth is now redundant

Both UseWindowsAuth and AuthenticationType are set and persisted. They could get out of sync. Consider making UseWindowsAuth a computed property (=> AuthenticationType == "Windows") or removing it (ties back to the migration issue above).

Minor: _isDialogOpen static flag

Uses Closing event — if the dialog crashes before that fires, the flag stays true permanently and MFA connections are blocked forever. Closed event is safer since it always fires.


Summary

Issue Severity
Backward compatibility break for existing SQL auth users Must fix
AADSTS50126 treated as cancellation (wrong credentials ≠ cancel) Must fix
IsMfaCancelledException duplicated 3x Nice to have
Magic strings for auth types Nice to have
Unreachable second MFA check Cleanup
ClosingClosed for dialog flag Minor

@ClaudioESSilva
Copy link
Contributor Author

@erikdarlingdata give it another try

Copy link
Owner

@erikdarlingdata erikdarlingdata left a comment

Choose a reason for hiding this comment

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

All six issues from the first review have been addressed:

  • Backward compatibility: UseWindowsAuth is now a computed property with a migration setter — old configs work correctly
  • AADSTS50126 removed: Only explicit user cancellation messages are detected now
  • Shared helper: MfaAuthenticationHelper eliminates the 3x duplication
  • Constants: AuthenticationTypes class replaces magic strings throughout
  • Unreachable check: Combined into single block with ternary
  • Closed event: Safer than Closing for the dialog flag

One minor nit: MigrateServerAuthentication() is effectively a no-op (just debug logging) since migration happens in the UseWindowsAuth setter. Not blocking.

LGTM — nice work on the update, Claudio!

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