a365 setup blueprint fails on macOS 15.x with a PlatformNotSupportedException even after PR #290
introduced a device code fallback for browser auth failures. The fix in #290 only patched
AuthenticationService.AuthenticateInteractivelyAsync, but BlueprintSubcommand has two separate
authentication paths that bypass AuthenticationService entirely and directly use MsalBrowserCredential
without any device code fallback.
Symptoms
The user sees the following in the output before the command fails:
IMPORTANT: You must grant consent for all required permissions.
Successfully authenticated to Microsoft Graph!
Successfully authenticated to Microsoft Graph
Opening browser for authentication...
WARNING: Browser authentication is not supported on this platform: macOS 15.3.1
WARNING: Could not retrieve current user for sponsors field: Browser authentication is not supported on this platform (macOS 15.3.1)
Opening browser for authentication...
WARNING: Browser authentication is not supported on this platform: macOS 15.3.1
ERROR: Failed to acquire MSAL Graph access token
ERROR: Failed to extract access token from Graph client
ERROR: Failed to create agent blueprint
ERROR: Microsoft Graph API operation failed: Create Agent Blueprint
Blueprint creation failed.
Root Cause
PR #290 added the PlatformNotSupportedException → device code fallback inside
AuthenticationService.AuthenticateInteractivelyAsync. However, BlueprintSubcommand has two
authentication paths that never call AuthenticationService:
Gap 1 — AcquireMsalGraphTokenAsync (no fallback)
BlueprintSubcommand.AcquireMsalGraphTokenAsync creates MsalBrowserCredential and calls
GetTokenAsync directly. When MSAL throws PlatformNotSupportedException on macOS, it is
wrapped as MsalAuthenticationFailedException by MsalBrowserCredential, but the caller only
has a generic catch (Exception) that logs the error and returns null. There is no fallback
to device code flow.
Gap 2 — InteractiveGraphAuthService + lazy token acquisition
BlueprintSubcommand.GetAuthenticatedGraphClientAsync delegates to InteractiveGraphAuthService,
which creates a MsalBrowserCredential and passes it to the GraphServiceClient constructor.
The critical issue: GraphServiceClient acquires tokens lazily — only when the first actual
Graph API call is made. The GraphServiceClient constructor always succeeds, which causes
InteractiveGraphAuthService to log "Successfully authenticated to Microsoft Graph!" prematurely
and return a client backed by a non-functional credential.
The try/catch block in InteractiveGraphAuthService.GetAuthenticatedGraphClientAsync never fires
for PlatformNotSupportedException because the exception surfaces later, from inside the Graph SDK,
when an API call is attempted. At that point there is no recovery path — the credential is already
baked into the GraphServiceClient instance with no fallback.
This is also a code duplication problem: the device code fallback logic exists in
AuthenticationService but must now be replicated (or properly centralized) for every code path
that uses MsalBrowserCredential directly.
Affected Files
| File |
Issue |
Commands/SetupSubcommands/BlueprintSubcommand.cs |
AcquireMsalGraphTokenAsync — no device code fallback |
Services/InteractiveGraphAuthService.cs |
No device code fallback; eagerly logs success before token is acquired |
Fix
Fix 1 — AcquireMsalGraphTokenAsync
Add a catch for MsalAuthenticationFailedException with inner PlatformNotSupportedException.
On macOS (or any platform where browser auth is unsupported), fall back to DeviceCodeCredential
using the same clientAppId and tenantId.
Fix 2 — InteractiveGraphAuthService.GetAuthenticatedGraphClientAsync
Before constructing the GraphServiceClient, eagerly acquire a token with MsalBrowserCredential
to detect platform support at construction time. If MsalAuthenticationFailedException with inner
PlatformNotSupportedException is caught, fall back to DeviceCodeCredential and build the
GraphServiceClient with that credential instead. This ensures the "Successfully authenticated"
log only appears after authentication has actually succeeded.
Related
a365 setup blueprintfails on macOS 15.x with aPlatformNotSupportedExceptioneven after PR #290introduced a device code fallback for browser auth failures. The fix in #290 only patched
AuthenticationService.AuthenticateInteractivelyAsync, butBlueprintSubcommandhas two separateauthentication paths that bypass
AuthenticationServiceentirely and directly useMsalBrowserCredentialwithout any device code fallback.
Symptoms
The user sees the following in the output before the command fails:
Root Cause
PR #290 added the
PlatformNotSupportedException → device codefallback insideAuthenticationService.AuthenticateInteractivelyAsync. However,BlueprintSubcommandhas twoauthentication paths that never call
AuthenticationService:Gap 1 —
AcquireMsalGraphTokenAsync(no fallback)BlueprintSubcommand.AcquireMsalGraphTokenAsynccreatesMsalBrowserCredentialand callsGetTokenAsyncdirectly. When MSAL throwsPlatformNotSupportedExceptionon macOS, it iswrapped as
MsalAuthenticationFailedExceptionbyMsalBrowserCredential, but the caller onlyhas a generic
catch (Exception)that logs the error and returnsnull. There is no fallbackto device code flow.
Gap 2 —
InteractiveGraphAuthService+ lazy token acquisitionBlueprintSubcommand.GetAuthenticatedGraphClientAsyncdelegates toInteractiveGraphAuthService,which creates a
MsalBrowserCredentialand passes it to theGraphServiceClientconstructor.The critical issue:
GraphServiceClientacquires tokens lazily — only when the first actualGraph API call is made. The
GraphServiceClientconstructor always succeeds, which causesInteractiveGraphAuthServiceto log "Successfully authenticated to Microsoft Graph!" prematurelyand return a client backed by a non-functional credential.
The try/catch block in
InteractiveGraphAuthService.GetAuthenticatedGraphClientAsyncnever firesfor
PlatformNotSupportedExceptionbecause the exception surfaces later, from inside the Graph SDK,when an API call is attempted. At that point there is no recovery path — the credential is already
baked into the
GraphServiceClientinstance with no fallback.This is also a code duplication problem: the device code fallback logic exists in
AuthenticationServicebut must now be replicated (or properly centralized) for every code paththat uses
MsalBrowserCredentialdirectly.Affected Files
Commands/SetupSubcommands/BlueprintSubcommand.csAcquireMsalGraphTokenAsync— no device code fallbackServices/InteractiveGraphAuthService.csFix
Fix 1 —
AcquireMsalGraphTokenAsyncAdd a catch for
MsalAuthenticationFailedExceptionwith innerPlatformNotSupportedException.On macOS (or any platform where browser auth is unsupported), fall back to
DeviceCodeCredentialusing the same
clientAppIdandtenantId.Fix 2 —
InteractiveGraphAuthService.GetAuthenticatedGraphClientAsyncBefore constructing the
GraphServiceClient, eagerly acquire a token withMsalBrowserCredentialto detect platform support at construction time. If
MsalAuthenticationFailedExceptionwith innerPlatformNotSupportedExceptionis caught, fall back toDeviceCodeCredentialand build theGraphServiceClientwith that credential instead. This ensures the "Successfully authenticated"log only appears after authentication has actually succeeded.
Related