Skip to content

macOS Browser Auth Fallback Not Applied to Blueprint Authentication Paths #308

@sellakumaran

Description

@sellakumaran

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

Metadata

Metadata

Assignees

Labels

P1Very high prioritybugSomething isn't workingsecuritySecurity-related issue

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions