From d33ab13b7ef11f33fa4b8a4eca7949a79c95a144 Mon Sep 17 00:00:00 2001 From: pmaytak <34331512+pmaytak@users.noreply.github.com> Date: Tue, 4 Aug 2020 23:07:16 -0700 Subject: [PATCH] Fix validating B2C issuer with tfp in issuer URI. (#340) * Fix validating B2C issuer with tfp in issuer URI. --- .../Constants/IDWebErrorMessage.cs | 1 + .../Microsoft.Identity.Web.xml | 4 ++-- .../Resource/AadIssuerValidator.cs | 16 ++++++++++++++-- .../TestConstants.cs | 1 + .../Resource/AadIssuerValidatorTests.cs | 19 +++++++++++++++++++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.Identity.Web/Constants/IDWebErrorMessage.cs b/src/Microsoft.Identity.Web/Constants/IDWebErrorMessage.cs index 5cbdf1e25..dba319244 100644 --- a/src/Microsoft.Identity.Web/Constants/IDWebErrorMessage.cs +++ b/src/Microsoft.Identity.Web/Constants/IDWebErrorMessage.cs @@ -33,6 +33,7 @@ internal static class IDWebErrorMessage public const string IssuerMetadataUrlIsRequired = "IDW10301: Azure AD Issuer metadata address URL is required. "; public const string NoMetadataDocumentRetrieverProvided = "IDW10302: No metadata document retriever is provided. "; public const string IssuerDoesNotMatchValidIssuers = "IDW10303: Issuer: '{0}', does not match any of the valid issuers provided for this application. "; + public const string B2CTfpIssuerNotSupported = "IDW10304: Microsoft Identity Web does not support a B2C issuer with 'tfp' in the URI. See https://aka.ms/ms-id-web/b2c-issuer for details."; // Protocol IDW10400 = "IDW10400:" public const string TenantIdClaimNotPresentInToken = "IDW10401: Neither `tid` nor `tenantId` claim is present in the token obtained from Microsoft identity platform. "; diff --git a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml index c66b3cd72..b80c2e436 100644 --- a/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml +++ b/src/Microsoft.Identity.Web/Microsoft.Identity.Web.xml @@ -1541,7 +1541,7 @@ Creates an MSAL confidential client application. - + Gets an access token for a downstream API on behalf of the user described by its claimsPrincipal. @@ -1552,7 +1552,7 @@ on behalf of the user described in the claimsPrincipal. Azure AD B2C user flow to target. - + Gets an access token for a downstream API on behalf of the user which account is passed as an argument. diff --git a/src/Microsoft.Identity.Web/Resource/AadIssuerValidator.cs b/src/Microsoft.Identity.Web/Resource/AadIssuerValidator.cs index 1f3a7f0be..99557ee40 100644 --- a/src/Microsoft.Identity.Web/Resource/AadIssuerValidator.cs +++ b/src/Microsoft.Identity.Web/Resource/AadIssuerValidator.cs @@ -199,7 +199,11 @@ private static string GetTenantIdFromToken(SecurityToken securityToken) return string.Empty; } - // The AAD "iss" claims contains the tenant ID in its value. The URI is {domain}/{tid}/v2.0 + // The AAD "iss" claims contains the tenant ID in its value. + // The URI can be + // - {domain}/{tid}/v2.0 + // - {domain}/{tid}/v2.0/ + // - {domain}/{tfp}/{tid}/{userFlow}/v2.0/ private static string GetTenantIdFromIss(string iss) { if (string.IsNullOrEmpty(iss)) @@ -209,11 +213,19 @@ private static string GetTenantIdFromIss(string iss) var uri = new Uri(iss); - if (uri.Segments.Length > 1) + if (uri.Segments.Length == 3) { return uri.Segments[1].TrimEnd('/'); } + if (uri.Segments.Length == 5 && uri.Segments[1].TrimEnd('/') == ClaimConstants.Tfp) + { + throw new SecurityTokenInvalidIssuerException( + string.Format( + CultureInfo.InvariantCulture, + IDWebErrorMessage.B2CTfpIssuerNotSupported)); + } + return string.Empty; } } diff --git a/tests/Microsoft.Identity.Web.Test.Common/TestConstants.cs b/tests/Microsoft.Identity.Web.Test.Common/TestConstants.cs index c4b0d9aeb..b6c97162f 100644 --- a/tests/Microsoft.Identity.Web.Test.Common/TestConstants.cs +++ b/tests/Microsoft.Identity.Web.Test.Common/TestConstants.cs @@ -65,6 +65,7 @@ public static class TestConstants public const string B2CIssuer2 = B2CInstance2 + "/" + B2CTenantAsGuid + "/v2.0"; public const string B2CCustomDomainIssuer = B2CCustomDomainInstance + "/" + B2CTenantAsGuid + "/v2.0"; public const string Scopes = "openid profile offline_access api://someapi"; + public const string B2CIssuerTfp = B2CInstance + "/" + ClaimConstants.Tfp + "/" + B2CTenantAsGuid + "/" + B2CSignUpSignInUserFlow + "/v2.0"; // Claims public const string ClaimNameTid = "tid"; diff --git a/tests/Microsoft.Identity.Web.Test/Resource/AadIssuerValidatorTests.cs b/tests/Microsoft.Identity.Web.Test/Resource/AadIssuerValidatorTests.cs index d9d8878c7..51c8d4052 100644 --- a/tests/Microsoft.Identity.Web.Test/Resource/AadIssuerValidatorTests.cs +++ b/tests/Microsoft.Identity.Web.Test/Resource/AadIssuerValidatorTests.cs @@ -340,5 +340,24 @@ public void Validate_FromCustomB2CAuthority_ValidateSuccessfully() ValidIssuers = new[] { TestConstants.B2CCustomDomainIssuer }, }); } + + [Fact] + public void Validate_FromB2CAuthority_WithTfpIssuer_ThrowsException() + { + Claim issClaim = new Claim(TestConstants.ClaimNameIss, TestConstants.B2CIssuerTfp); + JwtSecurityToken jwtSecurityToken = new JwtSecurityToken(issuer: TestConstants.B2CIssuerTfp, claims: new[] { issClaim }); + + AadIssuerValidator validator = AadIssuerValidator.GetIssuerValidator(TestConstants.B2CAuthorityWithV2); + + var exception = Assert.Throws(() => + validator.Validate( + TestConstants.B2CIssuerTfp, + jwtSecurityToken, + new TokenValidationParameters() + { + ValidIssuers = new[] { TestConstants.B2CIssuerTfp }, + })); + Assert.Equal(IDWebErrorMessage.B2CTfpIssuerNotSupported, exception.Message); + } } }