feat(get-token): add --device-code flag and auto-fallback for Exchange Graph scopes#423
Open
lwokeray wants to merge 1 commit into
Open
Conversation
…e Graph scopes
WAM (Windows Account Manager) rejects Exchange-specific Microsoft Graph delegated
scopes — MailboxSettings.ReadWrite, ExchangeMessageTrace.Read.All, Mail.ReadWrite,
etc. — with ApiContractViolation / "declined scopes are present". This is distinct
from a consent-not-granted failure (0xcaa90019): admin consent HAS been granted
via oauth2PermissionGrants, but WAM's internal scope validator does not recognise
Exchange scope strings. Device code flow bypasses the WAM broker and succeeds.
Changes:
- AuthenticationConstants: add WamDeclinedScopesError and WamApiContractViolation
constants that identify the "declined scopes" error pattern from WAM output
- MsalBrowserCredential: extend the WAM catch clause to also match the declined-
scopes ApiContractViolation error; automatically fall back to device code flow
(same path already used for CAP / device-compliance WAM errors)
- GetTokenSubcommand: add --device-code flag that forces device code flow
(useInteractiveBrowser: false) as an explicit escape hatch for Exchange scopes
- Tests: add option-existence tests for --device-code; add constant tests
documenting the WAM declined-scopes error pattern and its distinction from
the consent-not-granted error
Usage after this change:
# Automatic: WAM fails, CLI falls back to device code silently
a365 develop get-token --resource-id 00000003-0000-0000-c000-000000000000 \
--scopes "MailboxSettings.ReadWrite ExchangeMessageTrace.Read.All"
# Explicit: skip WAM entirely, go straight to device code
a365 develop get-token --resource-id 00000003-0000-0000-c000-000000000000 \
--scopes "MailboxSettings.ReadWrite ExchangeMessageTrace.Read.All" \
--device-code
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@lwokeray please read the following Contributor License Agreement(CLA). If you agree with the CLA, please reply with the following information.
Contributor License AgreementContribution License AgreementThis Contribution License Agreement (“Agreement”) is agreed to by the party signing below (“You”),
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
--device-codeflag toa365 develop get-tokenso users can bypass WAM for Exchange-specific Graph scopes that WAM rejectsMsalBrowserCredentialto automatically detect WAMApiContractViolation+ "declined scopes are present" errors and fall back to device code, rather than throwingWamDeclinedScopesError,WamApiContractViolation) toAuthenticationConstantsto document and centralize the known WAM error signaturesBackground
WAM (Windows Account Manager) rejects Exchange-specific Microsoft Graph scopes such as
MailboxSettings.ReadWriteandExchangeMessageTrace.Read.Allwith anApiContractViolationerror containing "declined scopes are present". This internal error code (593794722 decimal) does not begin with0xcaa, so the existing WAM fallback logic inMsalBrowserCredentialdoes not trigger — the exception is rethrown and the user sees an unhelpful MSAL error.Changes
AuthenticationConstants.csWamDeclinedScopesError = "declined scopes are present"— substring matched against WAMApiContractViolationmessagesWamApiContractViolation = "ApiContractViolation"— the WAM error classification that accompanies declined scopesMsalBrowserCredential.csIsWamDeclinedScopesError(MsalException)private static helper — returnstruewhen both WAM error strings are present in the exception message|| IsWamDeclinedScopesError(ex)AcquireTokenWithDeviceCodeFallbackAsync, so Exchange-scope requests automatically retry via device code without user interventionGetTokenSubcommand.cs--device-codeoption (bool, defaultfalse) with description explaining WAM limitation and Exchange scope use-caseAcquireAndDisplayTokenAsync,AcquireAndDisplayManifestTokensAsync, andAcquireTokenAsyncAcquireTokenAsyncpassesuseInteractiveBrowser: !useDeviceCodetoAuthenticationService.GetAccessTokenWithScopesAsyncTests
GetTokenSubcommandTests.cs: addedCreateCommand_ShouldHaveDeviceCodeOption,CreateCommand_DeviceCodeOption_DescriptionShouldMentionExchangeScopes; updatedCreateCommand_ShouldHaveAllRequiredOptions(count 8 → 9)AuthenticationConstantsTests.cs: addedWamDeclinedScopesError_ShouldMatchKnownWamErrorSubstring,WamApiContractViolation_ShouldMatchKnownWamErrorClassification,WamDeclinedScopesError_ShouldBeDifferentFromWamConsentRequiredErrorTest plan
dotnet build dirs.proj --configuration Release— 0 errors, 0 warningsdotnet test tests.proj --configuration Release— all 90 tests passa365 develop get-token --resource-id 00000003-0000-0000-c000-000000000000 --scopes "MailboxSettings.ReadWrite ExchangeMessageTrace.Read.All" --device-code— prompts with device code URL, acquires token successfully--device-codeon a machine where WAM rejects Exchange scopes: confirm automatic fallback warning appears and device code flow is used🤖 Generated with Claude Code