Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
3 changes: 3 additions & 0 deletions src/client/Microsoft.Identity.Client/Internal/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ internal static class Constants

public const string CcsRoutingHintHeader = "x-anchormailbox";
public const string AadThrottledErrorCode = "AADSTS50196";
public const string AadAccountTypeAndResourceIncompatibleErrorCode = "AADSTS500207";
public const string AadMissingScopeErrorCode = "AADSTS900144";

//Represents 5 minutes in Unit time stamp
public const int DefaultJitterRangeInSeconds = 300;
public static readonly TimeSpan AccessTokenExpirationBuffer = TimeSpan.FromMinutes(5);
Expand Down
1 change: 1 addition & 0 deletions src/client/Microsoft.Identity.Client/MsalErrorMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -442,5 +442,6 @@ public static string InvalidTokenProviderResponseValue(string invalidValueName)
public const string RegionRequiredForMtlsPopMessage = "Regional auto-detect failed. mTLS Proof-of-Possession requires a region to be specified, as there is no global endpoint for mTLS. See https://aka.ms/msal-net-pop for details.";
public const string ForceRefreshAndTokenHasNotCompatible = "Cannot specify ForceRefresh and AccessTokenSha256ToRefresh in the same request.";
public const string RequestTimeOut = "Request to the endpoint timed out.";
public const string MalformedOidcAuthorityFormat = "Possible cause: When using Entra External ID, you didn't append /v2.0, for example {0}/v2.0\"";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Text;
using Microsoft.Identity.Client.Http;
Expand Down Expand Up @@ -41,7 +42,7 @@ internal static MsalServiceException FromHttpResponse(
}
else
{
errorMessageToUse = errorMessage;
errorMessageToUse = errorMessage;
}

if (oAuth2Response.Claims == null)
Expand All @@ -64,6 +65,13 @@ internal static MsalServiceException FromHttpResponse(
innerException);
}

var authorityInfo = context.ServiceBundle.Config.Authority.AuthorityInfo;

if (IsOidcAuthorityError(authorityInfo, oAuth2Response.ErrorDescription))
{
errorMessage += string.Format(CultureInfo.InvariantCulture, MsalErrorMessage.MalformedOidcAuthorityFormat, $" {authorityInfo.CanonicalAuthority}");
}

ex ??= new MsalServiceException(errorCode, GetErrorMessage(errorMessage, httpResponse, context), innerException);

SetHttpExceptionData(ex, httpResponse);
Expand Down Expand Up @@ -108,6 +116,16 @@ private static bool IsThrottled(OAuth2ResponseBase oAuth2Response)
oAuth2Response.ErrorDescription.StartsWith(Constants.AadThrottledErrorCode);
}

private static bool IsOidcAuthorityError(AuthorityInfo authortyInfo, string ErrorDescription)
{
return authortyInfo is not null &&
authortyInfo.AuthorityType == AuthorityType.Generic && // Generic Oidc authority
!authortyInfo.CanonicalAuthority!.AbsoluteUri.EndsWith("/v2.0") && // Does not end with /v2.0
ErrorDescription != null &&
(ErrorDescription.StartsWith(Constants.AadAccountTypeAndResourceIncompatibleErrorCode) || // Certain error codes are returned
ErrorDescription.StartsWith(Constants.AadMissingScopeErrorCode));
}

internal static MsalServiceException FromBrokerResponse(
MsalTokenResponse msalTokenResponse,
string errorMessage)
Expand Down
7 changes: 4 additions & 3 deletions src/client/Microsoft.Identity.Client/OAuth2/OAuth2Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ private static void ThrowServerException(HttpResponse response, RequestContext r
MsalServiceException exceptionToThrow;
try
{
exceptionToThrow = ExtractErrorsFromTheResponse(response, ref shouldLogAsError);
exceptionToThrow = ExtractErrorsFromTheResponse(response, ref shouldLogAsError, requestContext);
}
catch (JsonException) // in the rare case we get an error response we cannot deserialize
{
Expand Down Expand Up @@ -304,7 +304,7 @@ private static void ThrowServerException(HttpResponse response, RequestContext r
throw exceptionToThrow;
}

private static MsalServiceException ExtractErrorsFromTheResponse(HttpResponse response, ref bool shouldLogAsError)
private static MsalServiceException ExtractErrorsFromTheResponse(HttpResponse response, ref bool shouldLogAsError, RequestContext context = null)
{
// In cases where the end-point is not found (404) response.body will be empty.
if (string.IsNullOrWhiteSpace(response.Body))
Expand Down Expand Up @@ -347,7 +347,8 @@ private static MsalServiceException ExtractErrorsFromTheResponse(HttpResponse re
return MsalServiceExceptionFactory.FromHttpResponse(
msalTokenResponse.Error,
msalTokenResponse.ErrorDescription,
response);
response,
context: context);
}

private Uri AddExtraQueryParams(Uri endPoint)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,10 @@ public static HttpResponseMessage CreateFailureTokenResponseMessage(
string error,
string subError = null,
string correlationId = null,
HttpStatusCode? customStatusCode = null)
HttpStatusCode? customStatusCode = null,
string errorCode = "AADSTS00000")
{
string message = "{\"error\":\"" + error + "\",\"error_description\":\"AADSTS00000: Error for test." +
string message = "{\"error\":\"" + error + "\",\"error_description\":\"" + errorCode + ": Error for test." +
"Trace ID: f7ec686c-9196-4220-a754-cd9197de44e9Correlation ID: " +
"04bb0cae-580b-49ac-9a10-b6c3316b1eaaTimestamp: 2015-09-16 07:24:55Z\"," +
"\"error_codes\":[70002,70008],\"timestamp\":\"2015-09-16 07:24:55Z\"," +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,18 @@ public static MockHttpMessageHandler AddFailureTokenEndpointResponse(
this MockHttpManager httpManager,
string error,
string authority = TestConstants.AuthorityCommonTenant,
string correlationId = null)
string correlationId = null,
string AadErrorCode = "AADSTS00000",
string expectedUrl = null)
{
var handler = new MockHttpMessageHandler()
{
ExpectedUrl = authority + "oauth2/v2.0/token",
ExpectedUrl = expectedUrl != null? expectedUrl : $"{authority}oauth2/v2.0/token",
ExpectedMethod = HttpMethod.Post,
ResponseMessage = MockHelpers.CreateFailureTokenResponseMessage(
error,
correlationId: correlationId)
error,
correlationId: correlationId,
errorCode: AadErrorCode)
};
httpManager.AddMockHandler(handler);
return handler;
Expand Down
5 changes: 5 additions & 0 deletions tests/Microsoft.Identity.Test.Common/TestConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ public static HashSet<string> s_scope
public const string CiamAuthorityMainFormat = "https://tenant.ciamlogin.com/";
public const string CiamAuthorityWithFriendlyName = "https://tenant.ciamlogin.com/tenant.onmicrosoft.com";
public const string CiamAuthorityWithGuid = "https://tenant.ciamlogin.com/aaaaaaab-aaaa-aaaa-cccc-aaaaaaaaaaaa";
public const string CiamCUDAuthority = "https://login.msidlabsciam.com/aaaaaaab-aaaa-aaaa-cccc-aaaaaaaaaaaa/v2.0";
public const string CiamCUDAuthorityMalformed = "https://login.msidlabsciam.com/aaaaaaab-aaaa-aaaa-cccc-aaaaaaaaaaaa";

public const string B2CLoginGlobal = ".b2clogin.com";
public const string B2CLoginUSGov = ".b2clogin.us";
Expand Down Expand Up @@ -229,6 +231,9 @@ public static HashSet<string> s_scope
public const string Pop = "PoP";
public const string FmiNodeClientId = "urn:microsoft:identity:fmi";

public const string AadAccountTypeAndResourceIncompatibleErrorCode = "AADSTS500207";
public const string AadMissingScopeErrorCode = "AADSTS900144";

public static IDictionary<string, string> ExtraQueryParameters
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -333,6 +334,60 @@ public async Task BadOidcResponse_ThrowsException_Async(string badOidcResponseTy
}
}

[TestMethod]
public async Task Oidc_Malformed_Failure_Async()
{
using (var httpManager = new MockHttpManager())
{
string authority = TestConstants.CiamCUDAuthorityMalformed;
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder
.Create(TestConstants.ClientId)
.WithHttpManager(httpManager)
.WithOidcAuthority(authority)
.WithClientSecret(TestConstants.ClientSecret)
.Build();

httpManager.AddMockHandler(
CreateOidcHttpHandler(authority + "/" + Constants.WellKnownOpenIdConfigurationPath));

httpManager.AddFailureTokenEndpointResponse(
error: "error",
AadErrorCode: TestConstants.AadAccountTypeAndResourceIncompatibleErrorCode,
expectedUrl: $"{TestConstants.CiamCUDAuthorityMalformed}/connect/token");

Assert.AreEqual(authority, app.Authority);
var confidentailClientApp = (ConfidentialClientApplication)app;
Assert.AreEqual(AuthorityType.Generic, confidentailClientApp.AuthorityInfo.AuthorityType);

var ex = await AssertException.TaskThrowsAsync<MsalServiceException>(() =>
app.AcquireTokenForClient(new[] { "api" })
.ExecuteAsync())
.ConfigureAwait(false);

Assert.IsTrue(ex.Message.Contains(
string.Format(
CultureInfo.InvariantCulture,
MsalErrorMessage.MalformedOidcAuthorityFormat,
TestConstants.CiamCUDAuthorityMalformed)));

httpManager.AddFailureTokenEndpointResponse(
error: "error",
AadErrorCode: TestConstants.AadMissingScopeErrorCode,
expectedUrl: $"{TestConstants.CiamCUDAuthorityMalformed}/connect/token");

ex = await AssertException.TaskThrowsAsync<MsalServiceException>(() =>
app.AcquireTokenForClient(new[] { "api" })
.ExecuteAsync())
.ConfigureAwait(false);

Assert.IsTrue(ex.Message.Contains(
string.Format(
CultureInfo.InvariantCulture,
MsalErrorMessage.MalformedOidcAuthorityFormat,
TestConstants.CiamCUDAuthorityMalformed)));
}
}

[TestMethod]
public async Task OidcIssuerValidation_ThrowsForNonMatchingIssuer_Async()
{
Expand Down
Loading