From e8763c4771222ac3c06ea9d53a1af0a2b3cb99a8 Mon Sep 17 00:00:00 2001 From: Yeming Liu Date: Thu, 3 Jul 2025 18:02:20 +1000 Subject: [PATCH 1/4] Refine error message about MFA --- src/Accounts/Accounts/ChangeLog.md | 1 + .../Accounts/CommonModule/ContextAdapter.cs | 5 +- .../IClaimsChallengeProcessor.cs | 2 +- .../Authentication/ClaimsChallengeHandler.cs | 13 +- src/Accounts/Authentication/MFA.dgml | 435 ++++++++++++++++++ .../Utilities/ClaimsChallengeUtilities.cs | 53 ++- .../Authenticators/MsalAccessToken.cs | 12 + 7 files changed, 506 insertions(+), 15 deletions(-) create mode 100644 src/Accounts/Authentication/MFA.dgml diff --git a/src/Accounts/Accounts/ChangeLog.md b/src/Accounts/Accounts/ChangeLog.md index 0a826675688b..24432ad4eaf2 100644 --- a/src/Accounts/Accounts/ChangeLog.md +++ b/src/Accounts/Accounts/ChangeLog.md @@ -19,6 +19,7 @@ --> ## Upcoming Release +* Refined the error message when a cmdlet fails because of policy violations about Multi-Factor Authentication (MFA) to provide more actionable guidance. ## Version 5.1.1 * Updated the date in the message about multi-factor authentication (MFA). For more details, see https://go.microsoft.com/fwlink/?linkid=2276971 diff --git a/src/Accounts/Accounts/CommonModule/ContextAdapter.cs b/src/Accounts/Accounts/CommonModule/ContextAdapter.cs index 281fc2dea145..fcd088d91932 100644 --- a/src/Accounts/Accounts/CommonModule/ContextAdapter.cs +++ b/src/Accounts/Accounts/CommonModule/ContextAdapter.cs @@ -200,14 +200,13 @@ internal async Task AuthenticationHelper(IAzureContext cont { var response = await next(request, cancelToken, cancelAction, signal); - if (response.MatchClaimsChallengePattern()) + if (response.MatchClaimsChallengePattern(out var claimsChallenge)) { //get token again with claims challenge if (accessToken is IClaimsChallengeProcessor processor) { try { - var claimsChallenge = ClaimsChallengeUtilities.GetClaimsChallenge(response); if (!string.IsNullOrEmpty(claimsChallenge)) { await processor.OnClaimsChallenageAsync(newRequest, claimsChallenge, cancelToken).ConfigureAwait(false); @@ -219,7 +218,7 @@ internal async Task AuthenticationHelper(IAzureContext cont } catch (AuthenticationFailedException e) { - throw e.WithAdditionalMessage(response?.GetWwwAuthenticateMessage()); + throw e.WithAdditionalMessage(ClaimsChallengeUtilities.FormatClaimsChallengeErrorMessage(claimsChallenge, await response?.Content?.ReadAsStringAsync())); } } } diff --git a/src/Accounts/Authentication/Authentication/IClaimsChallengeProcessor.cs b/src/Accounts/Authentication/Authentication/IClaimsChallengeProcessor.cs index 635adffb5a47..b7eb0ff83abb 100644 --- a/src/Accounts/Authentication/Authentication/IClaimsChallengeProcessor.cs +++ b/src/Accounts/Authentication/Authentication/IClaimsChallengeProcessor.cs @@ -29,7 +29,7 @@ public interface IClaimsChallengeProcessor /// The origin request that responds with a claim challenge /// Claims challenge string /// Cancellation token - /// Successful or not + /// A boolean indicated whether the request should be retried ValueTask OnClaimsChallenageAsync(HttpRequestMessage request, string claimsChallenge, CancellationToken cancellationToken); } } diff --git a/src/Accounts/Authentication/ClaimsChallengeHandler.cs b/src/Accounts/Authentication/ClaimsChallengeHandler.cs index 5f2c93d3150d..0ed037a93275 100644 --- a/src/Accounts/Authentication/ClaimsChallengeHandler.cs +++ b/src/Accounts/Authentication/ClaimsChallengeHandler.cs @@ -14,7 +14,6 @@ using Azure.Identity; using System; -using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; @@ -34,18 +33,19 @@ public ClaimsChallengeHandler(IClaimsChallengeProcessor claimsChallengeProcessor protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var response = await base.SendAsync(request, cancellationToken); - if (response.MatchClaimsChallengePattern()) + if (response.MatchClaimsChallengePattern(out var claimsChallenge)) { try { - if (await OnChallengeAsync(request, response, cancellationToken)) + if (await OnChallengeAsync(claimsChallenge, request, response, cancellationToken)) { return await base.SendAsync(request, cancellationToken); } } catch (AuthenticationFailedException e) { - throw e.WithAdditionalMessage(response?.GetWwwAuthenticateMessage()); + string additionalErrorMessage = ClaimsChallengeUtilities.FormatClaimsChallengeErrorMessage(claimsChallenge, await response?.Content?.ReadAsStringAsync()); + throw e.WithAdditionalMessage(additionalErrorMessage); } } return response; @@ -59,14 +59,13 @@ public virtual object Clone() /// Executed in the event a 401 response with a WWW-Authenticate authentication challenge header is received after the initial request. /// /// This implementation handles common authentication challenges such as claims challenges. Service client libraries may derive from this and extend to handle service specific authentication challenges. + /// /// The HttpMessage to be authenticated. /// Cancellation token /// /// A boolean indicated whether the request should be retried - protected virtual async Task OnChallengeAsync(HttpRequestMessage requestMessage, HttpResponseMessage responseMessage, CancellationToken cancellationToken) + protected virtual async Task OnChallengeAsync(string claimsChallenge, HttpRequestMessage requestMessage, HttpResponseMessage responseMessage, CancellationToken cancellationToken) { - var claimsChallenge = ClaimsChallengeUtilities.GetClaimsChallenge(responseMessage); - if (!string.IsNullOrEmpty(claimsChallenge)) { return await ClaimsChallengeProcessor.OnClaimsChallenageAsync(requestMessage, claimsChallenge, cancellationToken).ConfigureAwait(false); diff --git a/src/Accounts/Authentication/MFA.dgml b/src/Accounts/Authentication/MFA.dgml new file mode 100644 index 000000000000..f8d88c6ebdbf --- /dev/null +++ b/src/Accounts/Authentication/MFA.dgml @@ -0,0 +1,435 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Accounts/Authentication/Utilities/ClaimsChallengeUtilities.cs b/src/Accounts/Authentication/Utilities/ClaimsChallengeUtilities.cs index 0a387a0f97a7..53422380f1eb 100644 --- a/src/Accounts/Authentication/Utilities/ClaimsChallengeUtilities.cs +++ b/src/Accounts/Authentication/Utilities/ClaimsChallengeUtilities.cs @@ -33,7 +33,7 @@ static public class ClaimsChallengeUtilities private static readonly Regex AuthenticationChallengeRegex = new Regex(AuthenticationChallengePattern); private static readonly Regex ChallengeParameterRegex = new Regex(ChallengeParameterPattern); - public static string GetClaimsChallenge(HttpResponseMessage response) + private static string GetClaimsChallenge(HttpResponseMessage response) { return ParseWwwAuthenticate(response)? .Where((p) => string.Equals(p.Item1, "claims", StringComparison.OrdinalIgnoreCase)) @@ -46,15 +46,21 @@ public static string GetWwwAuthenticateMessage(this HttpResponseMessage response return string.Join(string.Empty, ParseWwwAuthenticate(response)?.Select(p => $"{p.Item1}: {p.Item2}{Environment.NewLine}")); } - public static bool MatchClaimsChallengePattern(this HttpResponseMessage response) + public static bool MatchClaimsChallengePattern(this HttpResponseMessage response, out string claimsChallenge) { - return response.StatusCode == System.Net.HttpStatusCode.Unauthorized && response.Headers.WwwAuthenticate?.Count > 0; + if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized && response.Headers.WwwAuthenticate?.Count > 0) + { + claimsChallenge = GetClaimsChallenge(response); + return true; + } + + claimsChallenge = null; + return false; } private static IEnumerable<(string, string)> ParseWwwAuthenticate(HttpResponseMessage response) { return Enumerable.Repeat(response, 1) - .Where(r => r.MatchClaimsChallengePattern()) .Select(r => r.Headers.WwwAuthenticate.FirstOrDefault().ToString()) .SelectMany(h => ParseChallenges(h)) .Where(c => string.Equals(c.Item1, "Bearer", StringComparison.OrdinalIgnoreCase)) @@ -80,5 +86,44 @@ public static bool MatchClaimsChallengePattern(this HttpResponseMessage response yield return (paramMatches[i].Groups[1].Value, paramMatches[i].Groups[2].Value); } } + + /// + /// Format the error message from the response content of the original failed request. + /// If the error is caused by CAE (continuous Access Evaluation), this will include why the request failed, and which policy was violated. + /// + /// + /// + /// + public static string FormatClaimsChallengeErrorMessage(string claimsChallenge, string responseContent) + { + var errorMessage = TryGetErrorMessageFromOriginalResponse(responseContent); + // Convert claimsChallenge to base64 + var claimsChallengeBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(claimsChallenge ?? string.Empty)); + // todo: use resource string + return $@"[This message needs review] Interactive authentication is required. Please run the following cmdlet and add additional parameters as needed: +Connect-AzAccount -ClaimsChallenge ""{claimsChallengeBase64}"" + +Error details: +{errorMessage}"; + } + + private static string TryGetErrorMessageFromOriginalResponse(string content) + { + if (string.IsNullOrWhiteSpace(content)) + { + return content; + } + + try + { + var parsedJson = Newtonsoft.Json.Linq.JToken.Parse(content); + return parsedJson["error"].Value("message"); + } + catch + { + // If parsing fails, return the original content + return content; + } + } } } diff --git a/src/Accounts/Authenticators/MsalAccessToken.cs b/src/Accounts/Authenticators/MsalAccessToken.cs index 8c5676feeafc..f4591741b33d 100644 --- a/src/Accounts/Authenticators/MsalAccessToken.cs +++ b/src/Accounts/Authenticators/MsalAccessToken.cs @@ -28,6 +28,10 @@ namespace Microsoft.Azure.PowerShell.Authenticators { + /// + /// Represents an access token obtained from Entra ID using MSAL (Microsoft Authentication Library). + /// Holds the access token, metadata about the user and tenant, and the context needed to renew the token. + /// public class MsalAccessToken : IAccessToken, IClaimsChallengeProcessor { public string AccessToken { get; private set; } @@ -121,6 +125,14 @@ private bool IsNearExpiration() return timeUntilExpiration < ExpirationThreshold; } + /// + /// Receives a claims challenge from the server and processes it to obtain a new access token. + /// Then updates the request with the new access token. + /// + /// + /// + /// + /// A boolean indicated whether the request should be retried. Throws if the reauth fails. public async ValueTask OnClaimsChallenageAsync(HttpRequestMessage request, string claimsChallenge, CancellationToken cancellationToken) { TracingAdapter.Information($"{DateTime.Now:T} - [ClaimsChallengeProcessor] Calling {TokenCredential.GetType().Name}.GetTokenAsync- claimsChallenge:'{claimsChallenge}'"); From ba41be0749a09d9110e6da7a61059584c030f8d8 Mon Sep 17 00:00:00 2001 From: Yeming Liu Date: Fri, 4 Jul 2025 13:50:20 +1000 Subject: [PATCH 2/4] Fix tests; add Tenant to parameters --- .../SilentReAuthByTenantCmdletTest.cs | 16 ++++++++++------ .../Utilities/ClaimsChallengeUtilities.cs | 5 +---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Accounts/Accounts.Test/SilentReAuthByTenantCmdletTest.cs b/src/Accounts/Accounts.Test/SilentReAuthByTenantCmdletTest.cs index 0d16a82ab2f3..2854ef09f061 100644 --- a/src/Accounts/Accounts.Test/SilentReAuthByTenantCmdletTest.cs +++ b/src/Accounts/Accounts.Test/SilentReAuthByTenantCmdletTest.cs @@ -55,9 +55,11 @@ public class SilentReAuthByTenantCmdletTest private const string fakeToken = "fakertoken"; private const string body200 = @"{{""value"":[{{""id"":""/tenants/{0}"",""tenantId"":""{0}"",""countryCode"":""US"",""displayName"":""AzureSDKTeam"",""domains"":[""AzureSDKTeam.onmicrosoft.com"",""azdevextest.com""],""tenantCategory"":""Home""}}]}}"; - private const string body401 = @"{""error"":{""code"":""AuthenticationFailed"",""message"":""Authentication failed.""}}"; - private const string WwwAuthenticateIP = @"Bearer authorization_uri=""https://login.windows.net/"", error=""invalid_token"", error_description=""Tenant IP Policy validate failed."", claims=""eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwidmFsdWUiOiIxNjEzOTgyNjA2In0sInhtc19ycF9pcGFkZHIiOnsidmFsdWUiOiIxNjcuMjIwLjI1NS40MSJ9fX0="""; - + private const string bodyErrorMessage401 = "Authentication failed."; + private const string body401 = @"{""error"":{""code"":""AuthenticationFailed"",""message"":"""+bodyErrorMessage401+@"""}}"; + private const string claimsChallengeBase64 = "eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwidmFsdWUiOiIxNjEzOTgyNjA2In0sInhtc19ycF9pcGFkZHIiOnsidmFsdWUiOiIxNjcuMjIwLjI1NS40MSJ9fX0="; + private const string WwwAuthenticateIP = @"Bearer authorization_uri=""https://login.windows.net/"", error=""invalid_token"", error_description=""Tenant IP Policy validate failed."", claims="""+ claimsChallengeBase64+@""""; + private const string identityExceptionMessage = "Exception from Azure Identity."; XunitTracingInterceptor xunitLogger; public class GetAzureRMTenantCommandMock : GetAzureRMTenantCommand @@ -171,7 +173,7 @@ public void SilentReauthenticateFailure() { return new ValueTask(new AccessToken(fakeToken, DateTimeOffset.Now.AddHours(1))); } - throw new CredentialUnavailableException("Exception from Azure Identity."); + throw new CredentialUnavailableException(identityExceptionMessage); } )); AzureSession.Instance.RegisterComponent(nameof(AzureCredentialFactory), () => mockAzureCredentialFactory.Object, true); @@ -191,8 +193,10 @@ public void SilentReauthenticateFailure() // Act cmdlet.InvokeBeginProcessing(); AuthenticationFailedException e = Assert.Throws(() => cmdlet.ExecuteCmdlet()); - string errorMessage = $"Exception from Azure Identity.{Environment.NewLine}authorization_uri: https://login.windows.net/{Environment.NewLine}error: invalid_token{Environment.NewLine}error_description: Tenant IP Policy validate failed.{Environment.NewLine}claims: eyJhY2Nlc3NfdG9rZW4iOnsibmJmIjp7ImVzc2VudGlhbCI6dHJ1ZSwidmFsdWUiOiIxNjEzOTgyNjA2In0sInhtc19ycF9pcGFkZHIiOnsidmFsdWUiOiIxNjcuMjIwLjI1NS40MSJ9fX0={Environment.NewLine}"; - Assert.Equal(errorMessage, e.Message); + Assert.Contains(identityExceptionMessage, e.Message); + Assert.Contains(bodyErrorMessage401, e.Message); + Assert.Contains("Connect-AzAccount", e.Message); + Assert.Contains(claimsChallengeBase64, e.Message); cmdlet.InvokeEndProcessing(); } finally diff --git a/src/Accounts/Authentication/Utilities/ClaimsChallengeUtilities.cs b/src/Accounts/Authentication/Utilities/ClaimsChallengeUtilities.cs index 53422380f1eb..a6d73e43e6c6 100644 --- a/src/Accounts/Authentication/Utilities/ClaimsChallengeUtilities.cs +++ b/src/Accounts/Authentication/Utilities/ClaimsChallengeUtilities.cs @@ -16,13 +16,10 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Net.Http; using System.Text; using System.Text.RegularExpressions; -using Microsoft.Azure.Commands.Profile.Utilities; - namespace Microsoft.Azure.Commands.Common.Authentication { static public class ClaimsChallengeUtilities @@ -101,7 +98,7 @@ public static string FormatClaimsChallengeErrorMessage(string claimsChallenge, s var claimsChallengeBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(claimsChallenge ?? string.Empty)); // todo: use resource string return $@"[This message needs review] Interactive authentication is required. Please run the following cmdlet and add additional parameters as needed: -Connect-AzAccount -ClaimsChallenge ""{claimsChallengeBase64}"" +Connect-AzAccount -Tenant (Get-AzContext).Tenant.Id -ClaimsChallenge ""{claimsChallengeBase64}"" Error details: {errorMessage}"; From cf3a3442ee441951c823418ca6120857af88a39c Mon Sep 17 00:00:00 2001 From: Yeming Liu Date: Tue, 15 Jul 2025 16:04:50 +1000 Subject: [PATCH 3/4] Update error message --- .../SilentReAuthByTenantCmdletTest.cs | 5 +++-- .../Accounts/CommonModule/ContextAdapter.cs | 21 ++++++++++--------- .../Authentication/ClaimsChallengeHandler.cs | 3 ++- .../Properties/Resources.Designer.cs | 13 ++++++++++++ .../Authentication/Properties/Resources.resx | 8 +++++++ .../Utilities/ClaimsChallengeUtilities.cs | 9 +++----- 6 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/Accounts/Accounts.Test/SilentReAuthByTenantCmdletTest.cs b/src/Accounts/Accounts.Test/SilentReAuthByTenantCmdletTest.cs index 2854ef09f061..1b3c0eeca518 100644 --- a/src/Accounts/Accounts.Test/SilentReAuthByTenantCmdletTest.cs +++ b/src/Accounts/Accounts.Test/SilentReAuthByTenantCmdletTest.cs @@ -36,6 +36,7 @@ using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; +using Microsoft.Azure.Commands.Common.Exceptions; namespace Microsoft.Azure.Commands.ResourceManager.Common.Test { @@ -192,8 +193,8 @@ public void SilentReauthenticateFailure() // Act cmdlet.InvokeBeginProcessing(); - AuthenticationFailedException e = Assert.Throws(() => cmdlet.ExecuteCmdlet()); - Assert.Contains(identityExceptionMessage, e.Message); + AzPSAuthenticationFailedException e = Assert.Throws(() => cmdlet.ExecuteCmdlet()); + Assert.DoesNotContain(identityExceptionMessage, e.Message); // cause it's misleading Assert.Contains(bodyErrorMessage401, e.Message); Assert.Contains("Connect-AzAccount", e.Message); Assert.Contains(claimsChallengeBase64, e.Message); diff --git a/src/Accounts/Accounts/CommonModule/ContextAdapter.cs b/src/Accounts/Accounts/CommonModule/ContextAdapter.cs index fcd088d91932..e0490507c7b8 100644 --- a/src/Accounts/Accounts/CommonModule/ContextAdapter.cs +++ b/src/Accounts/Accounts/CommonModule/ContextAdapter.cs @@ -12,22 +12,23 @@ // limitations under the License. // ---------------------------------------------------------------------------------- -using System; -using System.Threading; -using System.Threading.Tasks; -using System.Net.Http; -using System.Collections.Generic; +using Azure.Identity; +using Microsoft.Azure.Commands.Common.Authentication; using Microsoft.Azure.Commands.Common.Authentication.Abstractions; using Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core; +using Microsoft.Azure.Commands.Common.Exceptions; using Microsoft.Azure.Commands.Common.Utilities; using Microsoft.Azure.Commands.Profile.Models; -using System.Globalization; -using Microsoft.Azure.Commands.Common.Authentication; +using Microsoft.Azure.Commands.Profile.Properties; using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters; +using System; +using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Management.Automation; -using Microsoft.Azure.Commands.Profile.Properties; -using Azure.Identity; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; namespace Microsoft.Azure.Commands.Common { @@ -218,7 +219,7 @@ internal async Task AuthenticationHelper(IAzureContext cont } catch (AuthenticationFailedException e) { - throw e.WithAdditionalMessage(ClaimsChallengeUtilities.FormatClaimsChallengeErrorMessage(claimsChallenge, await response?.Content?.ReadAsStringAsync())); + throw new AzPSAuthenticationFailedException(ClaimsChallengeUtilities.FormatClaimsChallengeErrorMessage(claimsChallenge, await response?.Content?.ReadAsStringAsync()), null, e); } } } diff --git a/src/Accounts/Authentication/ClaimsChallengeHandler.cs b/src/Accounts/Authentication/ClaimsChallengeHandler.cs index 0ed037a93275..6f92bbff9fd2 100644 --- a/src/Accounts/Authentication/ClaimsChallengeHandler.cs +++ b/src/Accounts/Authentication/ClaimsChallengeHandler.cs @@ -13,6 +13,7 @@ // ---------------------------------------------------------------------------------- using Azure.Identity; +using Microsoft.Azure.Commands.Common.Exceptions; using System; using System.Net.Http; using System.Threading; @@ -45,7 +46,7 @@ protected override async Task SendAsync(HttpRequestMessage catch (AuthenticationFailedException e) { string additionalErrorMessage = ClaimsChallengeUtilities.FormatClaimsChallengeErrorMessage(claimsChallenge, await response?.Content?.ReadAsStringAsync()); - throw e.WithAdditionalMessage(additionalErrorMessage); + throw new AzPSAuthenticationFailedException(additionalErrorMessage, null, e); } } return response; diff --git a/src/Accounts/Authentication/Properties/Resources.Designer.cs b/src/Accounts/Authentication/Properties/Resources.Designer.cs index a3422228b12e..53b43fdb5a3c 100644 --- a/src/Accounts/Authentication/Properties/Resources.Designer.cs +++ b/src/Accounts/Authentication/Properties/Resources.Designer.cs @@ -276,6 +276,19 @@ public static string ErrorMessageMsalInteractionRequiredWithTid { } } + /// + /// Looks up a localized string similar to {0} + /// + ///Run the cmdlet below to authenticate interactively; additional parameters may be added as needed. + /// + ///Connect-AzAccount -Tenant (Get-AzContext).Tenant.Id -ClaimsChallenge "{1}". + /// + public static string ErrorMessageOfClaimsChallengeRequired { + get { + return ResourceManager.GetString("ErrorMessageOfClaimsChallengeRequired", resourceCulture); + } + } + /// /// Looks up a localized string similar to Authentication failed against resource {0}. User interaction is required. This may be due to the conditional access policy settings such as multi-factor authentication (MFA). Please rerun 'Connect-AzAccount' with additional parameter '-AuthScope {0}'.. /// diff --git a/src/Accounts/Authentication/Properties/Resources.resx b/src/Accounts/Authentication/Properties/Resources.resx index 09a52feac630..2d06378d1b8d 100644 --- a/src/Accounts/Authentication/Properties/Resources.resx +++ b/src/Accounts/Authentication/Properties/Resources.resx @@ -426,4 +426,12 @@ The scope of authenticating for SSH is not set. Please run "Set-AzEnvironment -Name {0} -SshAuthScope ..." to set it first. 0 = environment name + + {0} + +Run the cmdlet below to authenticate interactively; additional parameters may be added as needed. + +Connect-AzAccount -Tenant (Get-AzContext).Tenant.Id -ClaimsChallenge "{1}" + 0 = error message about policy violation; 1 = claims challenge in base64 + \ No newline at end of file diff --git a/src/Accounts/Authentication/Utilities/ClaimsChallengeUtilities.cs b/src/Accounts/Authentication/Utilities/ClaimsChallengeUtilities.cs index a6d73e43e6c6..3f0755d71477 100644 --- a/src/Accounts/Authentication/Utilities/ClaimsChallengeUtilities.cs +++ b/src/Accounts/Authentication/Utilities/ClaimsChallengeUtilities.cs @@ -12,6 +12,8 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using Microsoft.Azure.Commands.Common.Authentication.Properties; +using Microsoft.WindowsAzure.Commands.Common; using Microsoft.WindowsAzure.Commands.Utilities.Common; using System; using System.Collections.Generic; @@ -96,12 +98,7 @@ public static string FormatClaimsChallengeErrorMessage(string claimsChallenge, s var errorMessage = TryGetErrorMessageFromOriginalResponse(responseContent); // Convert claimsChallenge to base64 var claimsChallengeBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(claimsChallenge ?? string.Empty)); - // todo: use resource string - return $@"[This message needs review] Interactive authentication is required. Please run the following cmdlet and add additional parameters as needed: -Connect-AzAccount -Tenant (Get-AzContext).Tenant.Id -ClaimsChallenge ""{claimsChallengeBase64}"" - -Error details: -{errorMessage}"; + return string.Format(Resources.ErrorMessageOfClaimsChallengeRequired, errorMessage, claimsChallengeBase64); } private static string TryGetErrorMessageFromOriginalResponse(string content) From 0bf35e08dc8660175a08f5216dda06c433c8f19d Mon Sep 17 00:00:00 2001 From: Yeming Liu <11371776+isra-fel@users.noreply.github.com> Date: Tue, 15 Jul 2025 16:55:53 +1000 Subject: [PATCH 4/4] Delete src/Accounts/Authentication/MFA.dgml --- src/Accounts/Authentication/MFA.dgml | 435 --------------------------- 1 file changed, 435 deletions(-) delete mode 100644 src/Accounts/Authentication/MFA.dgml diff --git a/src/Accounts/Authentication/MFA.dgml b/src/Accounts/Authentication/MFA.dgml deleted file mode 100644 index f8d88c6ebdbf..000000000000 --- a/src/Accounts/Authentication/MFA.dgml +++ /dev/null @@ -1,435 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file