Skip to content

Commit 97879bf

Browse files
authored
Fix mac broker MAUI issues (#5328)
* For mac broker flows, limit the "start new message loop to guarantee go back to main thread" logic to console app only. Maui app should not start a new message loop since it already has one running. * Fix mac test app warnings * tmp * Try to fix pipeline build issue * Update NativeInterop version to 0.19.1 * Update mac console app version to 0.19.1 * Still use NativeInterop 0.18.1 * Add some logs
1 parent 0f28069 commit 97879bf

File tree

7 files changed

+135
-37
lines changed

7 files changed

+135
-37
lines changed

src/client/Microsoft.Identity.Client.Broker/RuntimeBroker.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,9 @@ public async Task<MsalTokenResponse> AcquireTokenInteractiveAsync(
186186
{
187187
if (readAccountResult.IsSuccess)
188188
{
189-
if (DesktopOsHelper.IsMac())
189+
if (DesktopOsHelper.IsMacConsoleApp())
190190
{
191+
_logger?.Verbose(() => "Mac console app calling AcquireTokenInteractivelyAsync from the main thread.");
191192
AuthResult result = null;
192193
await MacMainThreadScheduler.Instance().RunOnMainThreadAsync(async () =>
193194
{
@@ -201,7 +202,7 @@ await MacMainThreadScheduler.Instance().RunOnMainThreadAsync(async () =>
201202
var errorMessage = "Could not acquire token interactively.";
202203
msalTokenResponse = WamAdapters.HandleResponse(result, authenticationRequestParameters, _logger, errorMessage);
203204
}
204-
else // Non macOS
205+
else // Not mac console app scenaro
205206
{
206207
using (var result = await s_lazyCore.Value.AcquireTokenInteractivelyAsync(
207208
_parentHandle,
@@ -256,8 +257,9 @@ private async Task<MsalTokenResponse> SignInInteractivelyAsync(
256257
string loginHint = authenticationRequestParameters.LoginHint ?? authenticationRequestParameters?.Account?.Username;
257258
_logger?.Verbose(() => "[RuntimeBroker] AcquireTokenInteractive - login hint provided? " + !string.IsNullOrEmpty(loginHint));
258259

259-
if (DesktopOsHelper.IsMac())
260+
if (DesktopOsHelper.IsMacConsoleApp())
260261
{
262+
_logger?.Verbose(() => "Mac console app calling SignInInteractivelyAsync from the main thread.");
261263
AuthResult result = null;
262264
await MacMainThreadScheduler.Instance().RunOnMainThreadAsync(async () =>
263265
{
@@ -271,7 +273,7 @@ await MacMainThreadScheduler.Instance().RunOnMainThreadAsync(async () =>
271273
var errorMessage = "Could not sign in interactively.";
272274
msalTokenResponse = WamAdapters.HandleResponse(result, authenticationRequestParameters, _logger, errorMessage);
273275
}
274-
else // Non macOS
276+
else // Not mac console app scenaro
275277
{
276278
using (var result = await s_lazyCore.Value.SignInInteractivelyAsync(
277279
_parentHandle,
@@ -304,8 +306,9 @@ private async Task<MsalTokenResponse> AcquireTokenInteractiveDefaultUserAsync(
304306
_brokerOptions,
305307
_logger))
306308
{
307-
if (DesktopOsHelper.IsMac())
309+
if (DesktopOsHelper.IsMacConsoleApp())
308310
{
311+
_logger?.Verbose(() => "Mac console app calling SignInAsync from the main thread.");
309312
AuthResult result = null;
310313
await MacMainThreadScheduler.Instance().RunOnMainThreadAsync(async () =>
311314
{
@@ -318,7 +321,7 @@ await MacMainThreadScheduler.Instance().RunOnMainThreadAsync(async () =>
318321
var errorMessage = "Could not sign in interactively with the default OS account.";
319322
msalTokenResponse = WamAdapters.HandleResponse(result, authenticationRequestParameters, _logger, errorMessage);
320323
}
321-
else // Non macOS
324+
else // Not mac console app scenaro
322325
{
323326
using (NativeInterop.AuthResult result = await s_lazyCore.Value.SignInAsync(
324327
_parentHandle,

src/client/Microsoft.Identity.Client/Internal/Requests/Interactive/InteractiveRequest.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ protected override async Task<AuthenticationResult> ExecuteAsync(
6666
await ResolveAuthorityAsync().ConfigureAwait(false);
6767
cancellationToken.ThrowIfCancellationRequested();
6868
MsalTokenResponse tokenResponse = null;
69-
if (DesktopOsHelper.IsMac() && ServiceBundle.Config.IsBrokerEnabled)
69+
if (ServiceBundle.Config.IsBrokerEnabled && DesktopOsHelper.IsMacConsoleApp())
7070
{
7171
var macMainThreadScheduler = MacMainThreadScheduler.Instance();
7272
if (!macMainThreadScheduler.IsCurrentlyOnMainThread())
@@ -96,7 +96,10 @@ protected override async Task<AuthenticationResult> ExecuteAsync(
9696
}
9797
});
9898
if (!messageLoopStarted)
99+
{
100+
_logger?.Verbose(() => "Mac broker console app scenario needs to start a message loop internally.");
99101
macMainThreadScheduler.StartMessageLoop();
102+
}
100103
tokenResponse = await tcs.Task.ConfigureAwait(false);
101104
}
102105
else

src/client/Microsoft.Identity.Client/Platforms/Features/DesktopOS/MacNativeMethods.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,4 +205,64 @@ public static IntPtr GetGlobal(IntPtr handle, string symbol)
205205
return Marshal.PtrToStructure<IntPtr>(ptr);
206206
}
207207
}
208+
209+
internal static class LibObjc
210+
{
211+
private const string LibObjcLib = "/usr/lib/libobjc.dylib";
212+
213+
public static bool IsNsApplicationRunning()
214+
{
215+
// This function equals to calling objc code: `[[NSApplication sharedApplication] isRunning]`
216+
// The result indicates if there is an official Apple message loop running, we can use it as
217+
// whether it is UI-based app (MAUI) or console app.
218+
try
219+
{
220+
IntPtr nsApplicationClass = objc_getClass("NSApplication");
221+
if (nsApplicationClass == IntPtr.Zero)
222+
{
223+
return false;
224+
}
225+
226+
IntPtr sharedApplicationSelector = sel_registerName("sharedApplication");
227+
if (sharedApplicationSelector == IntPtr.Zero)
228+
{
229+
return false;
230+
}
231+
232+
IntPtr sharedApplication = objc_msgSend(nsApplicationClass, sharedApplicationSelector);
233+
if (sharedApplication == IntPtr.Zero)
234+
{
235+
return false;
236+
}
237+
238+
IntPtr isRunningSelector = sel_registerName("isRunning");
239+
if (isRunningSelector == IntPtr.Zero)
240+
{
241+
return false;
242+
}
243+
244+
IntPtr isRunningResult = objc_msgSend(sharedApplication, isRunningSelector);
245+
bool isRunning = isRunningResult != IntPtr.Zero;
246+
return isRunning;
247+
}
248+
catch (Exception)
249+
{
250+
return false;
251+
}
252+
}
253+
254+
[DllImport(LibObjcLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
255+
private static extern IntPtr objc_getClass(string name);
256+
257+
[DllImport(LibObjcLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
258+
private static extern IntPtr sel_registerName(string name);
259+
260+
[DllImport(LibObjcLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
261+
private static extern IntPtr objc_msgSend(IntPtr receiver, IntPtr selector);
262+
263+
[DllImport(LibObjcLib, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
264+
private static extern bool objc_msgSend_bool(IntPtr receiver, IntPtr selector);
265+
266+
}
267+
208268
}

src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/DesktopOsHelper.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,21 @@ private static bool IsInteractiveSessionLinux()
202202
return !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("DISPLAY"));
203203
}
204204
#endif
205+
206+
private static readonly Lazy<bool> _isMacConsoleApp = new Lazy<bool>(() => {
207+
#if SUPPORTS_WIN32
208+
return !LibObjc.IsNsApplicationRunning();
209+
#else
210+
return true;
211+
#endif
212+
});
213+
214+
public static bool IsMacConsoleApp()
215+
{
216+
if (!DesktopOsHelper.IsMac())
217+
return false;
218+
// Checking if NsApplication is running for one time would be enough.
219+
return _isMacConsoleApp.Value;
220+
}
205221
}
206222
}

tests/devapps/MacConsoleAppWithBroker/MacConsoleAppWithBroker.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<TargetPath>msalruntime_arm64.dylib</TargetPath>
2020
</None> -->
2121

22-
<PackageReference Include="Microsoft.Identity.Client.NativeInterop" Version="0.19.0" />
22+
<PackageReference Include="Microsoft.Identity.Client.NativeInterop" Version="0.19.1" />
2323

2424
</ItemGroup>
2525

tests/devapps/MacMauiAppWithBroker/MacMauiAppWithBroker.csproj

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
<ImplicitUsings>enable</ImplicitUsings>
2121
<Nullable>enable</Nullable>
2222

23+
<!-- Disable strong naming for MAUI app -->
24+
<SignAssembly>false</SignAssembly>
25+
2326
<!-- Display name -->
2427
<ApplicationTitle>MacMauiAppWithBroker</ApplicationTitle>
2528

@@ -74,10 +77,6 @@
7477
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
7578
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
7679
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.1" />
77-
78-
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
79-
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
80-
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.1" />
8180
</ItemGroup>
8281

8382
<!-- <ItemGroup>
@@ -90,11 +89,7 @@
9089
</ItemGroup> -->
9190

9291
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('OSX'))">
93-
<PackageReference Include="Microsoft.Identity.Client.NativeInterop" Version="0.18.2" />
94-
<NativeReference Include="$(HOME)/.nuget/packages/microsoft.identity.client.nativeinterop/0.18.2/runtimes/macos-arm64/native/msalruntime_arm64.dylib">
95-
<Kind>Dynamic</Kind>
96-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
97-
</NativeReference>
92+
<PackageReference Include="Microsoft.Identity.Client.NativeInterop" Version="0.19.1" />
9893
</ItemGroup>
9994

10095
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('OSX'))">

tests/devapps/MacMauiAppWithBroker/MainPage.xaml.cs

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ public MainPage()
1717
private void AppendLog(string message)
1818
{
1919
string newEntry = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}: {message}{Environment.NewLine}";
20-
MainThread.BeginInvokeOnMainThread(async () =>
20+
MainThread.BeginInvokeOnMainThread(() => _ = AppendLogAsync(newEntry));
21+
}
22+
23+
private async Task AppendLogAsync(string newEntry)
24+
{
25+
try
2126
{
2227
if (LogTextView.Text == null)
2328
{
@@ -43,43 +48,48 @@ private void AppendLog(string message)
4348
await Task.Delay(50).ConfigureAwait(false);
4449

4550
await LogScrollView.ScrollToAsync(0, LogTextView.Height, true).ConfigureAwait(false);
46-
});
51+
}
52+
catch (Exception ex)
53+
{
54+
// Log any exceptions to prevent crashes
55+
Console.WriteLine($"Error in AppendLogAsync: {ex.Message}");
56+
}
4757
}
4858

4959
private async void OnACIACSClicked(object sender, EventArgs e)
5060
{
5161
// Disable button to prevent multiple clicks
5262
SetButtonEnabled(CACIACSBtn, false);
53-
63+
5464
SemanticScreenReader.Announce(CACIACSBtn.Text);
5565

56-
PublicClientApplicationBuilder builder = PublicClientApplicationBuilder
66+
PublicClientApplicationBuilder builder = PublicClientApplicationBuilder
5767
.Create("04b07795-8ddb-461a-bbee-02f9e1bf7b46") // Azure CLI client id
5868
.WithRedirectUri("msauth.com.msauth.unsignedapp://auth")
5969
.WithAuthority("https://login.microsoftonline.com/organizations");
60-
70+
6171
builder = builder.WithLogging(SampleLogging);
6272

6373
builder = builder.WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.OSX)
64-
{
65-
ListOperatingSystemAccounts = false,
66-
MsaPassthrough = false,
67-
Title = "MSAL Dev App .NET FX"
68-
}
74+
{
75+
ListOperatingSystemAccounts = false,
76+
MsaPassthrough = false,
77+
Title = "MSAL Dev App .NET FX"
78+
}
6979
);
7080

71-
IPublicClientApplication pca = builder.Build();
72-
73-
try
81+
IPublicClientApplication pca = builder.Build();
82+
83+
try
7484
{
7585
AppendLog("Starting interactive authentication...");
76-
AcquireTokenInteractiveParameterBuilder interactiveBuilder = pca.AcquireTokenInteractive(new string[]{"https://graph.microsoft.com/.default"});
86+
AcquireTokenInteractiveParameterBuilder interactiveBuilder = pca.AcquireTokenInteractive(new string[] { "https://graph.microsoft.com/.default" });
7787
AuthenticationResult result = await interactiveBuilder.ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
7888
AppendLog($"Interactive auth successful. User: {result.Account.Username}");
7989

8090
IAccount account = result.Account;
8191
AppendLog("Starting silent authentication...");
82-
AcquireTokenSilentParameterBuilder silentBuilder = pca.AcquireTokenSilent(new string[]{"https://graph.microsoft.com/.default"}, account);
92+
AcquireTokenSilentParameterBuilder silentBuilder = pca.AcquireTokenSilent(new string[] { "https://graph.microsoft.com/.default" }, account);
8393
// AcquireTokenSilentParameterBuilder silentBuilder = pca.AcquireTokenSilent(new string[]{"service::ssl.live.com::MBI_SSL"}, account);
8494
result = await silentBuilder.ExecuteAsync(CancellationToken.None).ConfigureAwait(false);
8595
AppendLog($"Silent auth successful. Access token length: {result.AccessToken.Length}");
@@ -144,7 +154,12 @@ private void OnClearLogClicked(object sender, EventArgs e)
144154
// Disable the button temporarily
145155
SetButtonEnabled(ClearLogBtn, false);
146156

147-
MainThread.BeginInvokeOnMainThread(async () =>
157+
MainThread.BeginInvokeOnMainThread(() => _ = ClearLogAsync());
158+
}
159+
160+
private async Task ClearLogAsync()
161+
{
162+
try
148163
{
149164
LogTextView.Text = string.Empty;
150165
AppendLog("Log cleared");
@@ -154,13 +169,19 @@ private void OnClearLogClicked(object sender, EventArgs e)
154169

155170
// Re-enable the button
156171
SetButtonEnabled(ClearLogBtn, true);
157-
});
172+
}
173+
catch (Exception ex)
174+
{
175+
// Log any exceptions and ensure button is re-enabled
176+
Console.WriteLine($"Error in ClearLogAsync: {ex.Message}");
177+
SetButtonEnabled(ClearLogBtn, true);
178+
}
158179
}
159180

160181
// Helper method to enable or disable a button on the UI thread
161182
private void SetButtonEnabled(Button button, bool enabled)
162183
{
163-
MainThread.BeginInvokeOnMainThread(() =>
184+
MainThread.BeginInvokeOnMainThread(() =>
164185
{
165186
button.IsEnabled = enabled;
166187
});
@@ -170,8 +191,8 @@ private static void SampleLogging(LogLevel level, string message, bool containsP
170191
{
171192
try
172193
{
173-
string homeDirectory = Environment.GetEnvironmentVariable("HOME");
174-
string filePath = Path.Combine(homeDirectory, "msalnet.log");
194+
string? homeDirectory = Environment.GetEnvironmentVariable("HOME");
195+
string filePath = Path.Combine(homeDirectory ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "msalnet.log");
175196
// An example log path could be: /Users/fengga/Library/Containers/com.microsoft.MacMauiAppWithBroker/Data/msalnet.log
176197
using (StreamWriter writer = new StreamWriter(filePath, append: true))
177198
{

0 commit comments

Comments
 (0)