Skip to content

Refine error message about MFA #28124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: Az.Accounts-preview
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions src/Accounts/Accounts.Test/SilentReAuthByTenantCmdletTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -171,7 +173,7 @@ public void SilentReauthenticateFailure()
{
return new ValueTask<AccessToken>(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);
Expand All @@ -191,8 +193,10 @@ public void SilentReauthenticateFailure()
// Act
cmdlet.InvokeBeginProcessing();
AuthenticationFailedException e = Assert.Throws<AuthenticationFailedException>(() => 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
Expand Down
1 change: 1 addition & 0 deletions src/Accounts/Accounts/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions src/Accounts/Accounts/CommonModule/ContextAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,13 @@ internal async Task<HttpResponseMessage> 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);
Expand All @@ -219,7 +218,7 @@ internal async Task<HttpResponseMessage> AuthenticationHelper(IAzureContext cont
}
catch (AuthenticationFailedException e)
{
throw e.WithAdditionalMessage(response?.GetWwwAuthenticateMessage());
throw e.WithAdditionalMessage(ClaimsChallengeUtilities.FormatClaimsChallengeErrorMessage(claimsChallenge, await response?.Content?.ReadAsStringAsync()));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public interface IClaimsChallengeProcessor
/// <param name="request">The origin request that responds with a claim challenge</param>
/// <param name="claimsChallenge">Claims challenge string</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Successful or not</returns>
/// <returns>A boolean indicated whether the request should be retried</returns>
ValueTask<bool> OnClaimsChallenageAsync(HttpRequestMessage request, string claimsChallenge, CancellationToken cancellationToken);
}
}
13 changes: 6 additions & 7 deletions src/Accounts/Authentication/ClaimsChallengeHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

using Azure.Identity;
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -34,18 +33,19 @@ public ClaimsChallengeHandler(IClaimsChallengeProcessor claimsChallengeProcessor
protected override async Task<HttpResponseMessage> 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;
Expand All @@ -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.
/// </summary>
/// <remarks>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.</remarks>
/// <param name="claimsChallenge"></param>
/// <param name="requestMessage">The HttpMessage to be authenticated.</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <param name="responseMessage"></param>
/// <returns>A boolean indicated whether the request should be retried</returns>
protected virtual async Task<bool> OnChallengeAsync(HttpRequestMessage requestMessage, HttpResponseMessage responseMessage, CancellationToken cancellationToken)
protected virtual async Task<bool> 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);
Expand Down
Loading