From 9fdf5f5e5a5568d1aef1a434aed40d3cf7443e5a Mon Sep 17 00:00:00 2001 From: Khang Nguyen Date: Fri, 10 Apr 2026 15:24:47 +1000 Subject: [PATCH] fix: handle management.azure.com as ARM endpoint in AccessTokenAuthenticator When users sign in via Connect-AzAccount -AccessToken, calling Get-AzAccessToken -ResourceUrl 'https://management.azure.com/' would fail with '[AccessTokenAuthenticator] failed to retrieve access token for resource'. This is because the authenticator only matched the legacy management.core.windows.net URL as the ARM token audience, not the ResourceManager endpoint URL (management.azure.com). Both URLs are valid audiences for the same ARM access token. Add a check for environment.GetEndpoint(AzureEnvironment.Endpoint.ResourceManager) in the ARM token branch so that management.azure.com is correctly resolved to the stored access token. Fixes #28028 --- .../AccessTokenAuthenticatorTests.cs | 181 ++++++++++++++++++ .../AccessTokenAuthenticator.cs | 3 +- 2 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 src/Accounts/Authentication.Test/AuthenticatorsTest/AccessTokenAuthenticatorTests.cs diff --git a/src/Accounts/Authentication.Test/AuthenticatorsTest/AccessTokenAuthenticatorTests.cs b/src/Accounts/Authentication.Test/AuthenticatorsTest/AccessTokenAuthenticatorTests.cs new file mode 100644 index 000000000000..318889ab2825 --- /dev/null +++ b/src/Accounts/Authentication.Test/AuthenticatorsTest/AccessTokenAuthenticatorTests.cs @@ -0,0 +1,181 @@ +// ---------------------------------------------------------------------------------- +// +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Threading.Tasks; + +using Microsoft.Azure.Commands.Common.Authentication; +using Microsoft.Azure.Commands.Common.Authentication.Abstractions; +using Microsoft.Azure.PowerShell.Authenticators; +using Microsoft.WindowsAzure.Commands.ScenarioTest; + +using Xunit; +using Xunit.Abstractions; + +namespace Common.Authenticators.Test +{ + /// + /// Tests for . + /// Covers the bug reported in https://github.com/Azure/azure-powershell/issues/28028 where + /// Get-AzAccessToken -ResourceUrl "https://management.azure.com/" failed when signed in via + /// Connect-AzAccount -AccessToken, even though the stored ARM token is valid for that audience. + /// + public class AccessTokenAuthenticatorTests + { + private const string TestTenantId = "test-tenant-id"; + private const string TestUserId = "testuser@contoso.com"; + private const string FakeArmToken = "fake-arm-access-token"; + private const string FakeGraphToken = "fake-graph-access-token"; + private const string FakeKeyVaultToken = "fake-keyvault-access-token"; + + private readonly IAzureEnvironment _azureCloud = AzureEnvironment.PublicEnvironments["AzureCloud"]; + + public AccessTokenAuthenticatorTests(ITestOutputHelper output) + { + AzureSessionInitializer.InitializeAzureSession(); + } + + private AzureAccount CreateAccessTokenAccount() + { + var account = new AzureAccount + { + Id = TestUserId, + Type = AzureAccount.AccountType.AccessToken, + }; + account.SetProperty(AzureAccount.Property.AccessToken, FakeArmToken); + account.SetProperty(AzureAccount.Property.GraphAccessToken, FakeGraphToken); + account.SetProperty(AzureAccount.Property.KeyVaultAccessToken, FakeKeyVaultToken); + return account; + } + + private AccessTokenParameters CreateParameters(string resourceId, AzureAccount account = null) + { + return new AccessTokenParameters( + new InMemoryTokenCacheProvider(), + _azureCloud, + null, + TestTenantId, + resourceId, + account ?? CreateAccessTokenAccount()); + } + + /// + /// Verifies that the stored ARM access token is returned when the resource URL is + /// the legacy management endpoint (https://management.core.windows.net/). + /// This is the pre-existing working case. + /// + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public async Task ReturnsArmToken_WhenResourceIsManagementCoreWindowsNet() + { + var resourceId = "https://management.core.windows.net/"; + var parameters = CreateParameters(resourceId); + var authenticator = new AccessTokenAuthenticator(); + + var token = await authenticator.Authenticate(parameters); + + Assert.NotNull(token); + Assert.Equal(FakeArmToken, token.AccessToken); + Assert.Equal(TestUserId, token.UserId); + Assert.Equal(TestTenantId, token.TenantId); + } + + /// + /// Verifies that the stored ARM access token is returned when the resource URL is + /// https://management.azure.com/ — the Azure Resource Manager endpoint URL. + /// This covers the bug in https://github.com/Azure/azure-powershell/issues/28028. + /// + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public async Task ReturnsArmToken_WhenResourceIsManagementAzureCom() + { + // This is the resource URL that previously caused the error: + // "[AccessTokenAuthenticator] failed to retrieve access token for resource 'https://management.azure.com/'" + var resourceId = _azureCloud.GetEndpoint(AzureEnvironment.Endpoint.ResourceManager); + Assert.Equal("https://management.azure.com/", resourceId, StringComparer.OrdinalIgnoreCase); + + var parameters = CreateParameters(resourceId); + var authenticator = new AccessTokenAuthenticator(); + + var token = await authenticator.Authenticate(parameters); + + Assert.NotNull(token); + Assert.Equal(FakeArmToken, token.AccessToken); + Assert.Equal(TestUserId, token.UserId); + Assert.Equal(TestTenantId, token.TenantId); + } + + /// + /// Verifies that the KeyVault access token is returned when requesting the KeyVault resource. + /// + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public async Task ReturnsKeyVaultToken_WhenResourceIsKeyVaultEndpoint() + { + var resourceId = _azureCloud.GetEndpoint(AzureEnvironment.Endpoint.AzureKeyVaultServiceEndpointResourceId); + var parameters = CreateParameters(resourceId); + var authenticator = new AccessTokenAuthenticator(); + + var token = await authenticator.Authenticate(parameters); + + Assert.NotNull(token); + Assert.Equal(FakeKeyVaultToken, token.AccessToken); + } + + /// + /// Verifies that the AAD Graph access token is returned when requesting the Graph resource. + /// + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public async Task ReturnsGraphToken_WhenResourceIsGraphEndpoint() + { + var resourceId = _azureCloud.GetEndpoint(AzureEnvironment.Endpoint.GraphEndpointResourceId); + var parameters = CreateParameters(resourceId); + var authenticator = new AccessTokenAuthenticator(); + + var token = await authenticator.Authenticate(parameters); + + Assert.NotNull(token); + Assert.Equal(FakeGraphToken, token.AccessToken); + } + + /// + /// Verifies that an InvalidOperationException is thrown when requesting a resource + /// for which no token was provided at login time. + /// + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public async Task ThrowsInvalidOperationException_WhenResourceIsUnsupported() + { + var resourceId = "https://unsupported.resource.example.com/"; + var parameters = CreateParameters(resourceId); + var authenticator = new AccessTokenAuthenticator(); + + await Assert.ThrowsAsync(() => authenticator.Authenticate(parameters)); + } + + /// + /// Verifies that CanAuthenticate returns true only for AccessTokenParameters. + /// + [Fact] + [Trait(Category.AcceptanceType, Category.CheckIn)] + public void CanAuthenticate_ReturnsTrueForAccessTokenParameters() + { + var parameters = CreateParameters("https://management.azure.com/"); + var authenticator = new AccessTokenAuthenticator(); + + Assert.True(authenticator.CanAuthenticate(parameters)); + } + } +} diff --git a/src/Accounts/Authenticators/AccessTokenAuthenticator.cs b/src/Accounts/Authenticators/AccessTokenAuthenticator.cs index 5ba7c72b6f92..b2dcceb5ee1f 100644 --- a/src/Accounts/Authenticators/AccessTokenAuthenticator.cs +++ b/src/Accounts/Authenticators/AccessTokenAuthenticator.cs @@ -68,7 +68,8 @@ public override Task Authenticate(AuthenticationParameters paramet else if ((resourceId.EqualsInsensitively(environment.ActiveDirectoryServiceEndpointResourceId) || resourceId.EqualsInsensitively(AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId) || resourceId.EqualsInsensitively(environment.GetEndpoint(environment.ActiveDirectoryServiceEndpointResourceId)) || - resourceId.EqualsInsensitively(environment.GetEndpoint(AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId))) + resourceId.EqualsInsensitively(environment.GetEndpoint(AzureEnvironment.Endpoint.ActiveDirectoryServiceEndpointResourceId)) || + resourceId.EqualsInsensitively(environment.GetEndpoint(AzureEnvironment.Endpoint.ResourceManager))) && account.IsPropertySet(AzureAccount.Property.AccessToken)) { TracingAdapter.Information($"{DateTime.Now:T} - [AccessTokenAuthenticator] Creating access token - Tenant: '{tenant}', ResourceId: '{resourceId}', UserId: '{account.Id}'");