diff --git a/src/Microsoft.Identity.Web.OWIN/AppBuilderExtension.cs b/src/Microsoft.Identity.Web.OWIN/AppBuilderExtension.cs
index 55083dbc6..78fec614c 100644
--- a/src/Microsoft.Identity.Web.OWIN/AppBuilderExtension.cs
+++ b/src/Microsoft.Identity.Web.OWIN/AppBuilderExtension.cs
@@ -2,6 +2,8 @@
// Licensed under the MIT License.
using System;
+using System.Globalization;
+using System.Security.Authentication;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;
@@ -15,6 +17,7 @@
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Validators;
using Microsoft.Owin.Security.Jwt;
+using Microsoft.Owin.Security.Notifications;
using Microsoft.Owin.Security.OAuth;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
@@ -173,10 +176,23 @@ public static IAppBuilder AddMicrosoftIdentityWebApp(
{
ClientInfo? clientInfoFromServer = ClientInfo.CreateFromJson(clientInfo);
- if (clientInfoFromServer != null && clientInfoFromServer.UniqueTenantIdentifier != null && clientInfoFromServer.UniqueObjectIdentifier != null)
+ if (clientInfoFromServer != null)
{
- context.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimConstants.UniqueTenantIdentifier, clientInfoFromServer.UniqueTenantIdentifier));
- context.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimConstants.UniqueObjectIdentifier, clientInfoFromServer.UniqueObjectIdentifier));
+ if (clientInfoFromServer.UniqueTenantIdentifier != null)
+ {
+ RejectInternalClaims(context.AuthenticationTicket.Identity, ClaimConstants.UniqueTenantIdentifier, clientInfoFromServer.UniqueTenantIdentifier);
+ }
+
+ if (clientInfoFromServer.UniqueObjectIdentifier != null)
+ {
+ RejectInternalClaims(context.AuthenticationTicket.Identity, ClaimConstants.UniqueObjectIdentifier, clientInfoFromServer.UniqueObjectIdentifier);
+ }
+
+ if (clientInfoFromServer.UniqueTenantIdentifier != null && clientInfoFromServer.UniqueObjectIdentifier != null)
+ {
+ context.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimConstants.UniqueTenantIdentifier, clientInfoFromServer.UniqueTenantIdentifier));
+ context.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimConstants.UniqueObjectIdentifier, clientInfoFromServer.UniqueObjectIdentifier));
+ }
}
context.OwinContext.Environment.Remove(ClaimConstants.ClientInfo);
}
@@ -187,6 +203,8 @@ public static IAppBuilder AddMicrosoftIdentityWebApp(
if (clientInfoFromServer != null && clientInfoFromServer.UniqueTenantIdentifier != null && clientInfoFromServer.UniqueObjectIdentifier != null)
{
+ RejectInternalClaims(context.AuthenticationTicket.Identity, clientInfoFromServer.UniqueTenantIdentifier, clientInfoFromServer.UniqueObjectIdentifier);
+
context.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimConstants.UniqueTenantIdentifier, clientInfoFromServer.UniqueTenantIdentifier));
context.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimConstants.UniqueObjectIdentifier, clientInfoFromServer.UniqueObjectIdentifier));
}
@@ -238,5 +256,24 @@ public static IAppBuilder AddMicrosoftIdentityWebApp(
return app.UseOpenIdConnectAuthentication(options);
}
+
+ ///
+ /// The SDK injects 2 claims named uuid and utid into the ClaimsPrincipal, from ESTS's client_info. These represent
+ /// the home oid and home tid and together they form the AccountId, which MSAL uses as cache key for web site
+ /// scenarios. In case the app owner defines claims with the same name to be added to the ID Token, this creates
+ /// a conflict with the reserved claims Id.Web injects, and it is better to throw a meaningful error. See issue 2968 for details.
+ ///
+ /// The associated to the logged-in user
+ /// The claim type
+ /// The claim value
+ /// The contains internal claims that are used internal use by this library
+ private static void RejectInternalClaims(ClaimsIdentity identity, string claimType, string claimValue)
+ {
+ var identityClaim = identity.FindFirst(c => c.Type == claimType);
+ if (identityClaim != null && !string.Equals(claimValue, identityClaim.Value, StringComparison.OrdinalIgnoreCase))
+ {
+ throw new AuthenticationException(string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.InternalClaimDetected, claimType));
+ }
+ }
}
}
diff --git a/src/Microsoft.Identity.Web.OWIN/Microsoft.Identity.Web.OWIN.xml b/src/Microsoft.Identity.Web.OWIN/Microsoft.Identity.Web.OWIN.xml
index 8c0da7f69..0f91fd4c9 100644
--- a/src/Microsoft.Identity.Web.OWIN/Microsoft.Identity.Web.OWIN.xml
+++ b/src/Microsoft.Identity.Web.OWIN/Microsoft.Identity.Web.OWIN.xml
@@ -55,6 +55,15 @@
Configuration section in which to read the options.
The app builder to chain.
+
+
+ Rejects the internal claims, if present.
+
+ The associated to the logged-in user
+ The tenant identifier (i.e. >)
+ The object identifier (i.e. >)
+ The contains internal claims that are used internal use by this library
+
Extension methods to retrieve a Graph service client and interfaces used
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/IDWebErrorMessage.cs b/src/Microsoft.Identity.Web.TokenAcquisition/IDWebErrorMessage.cs
index fc989d5b6..89963ae4f 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/IDWebErrorMessage.cs
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/IDWebErrorMessage.cs
@@ -37,6 +37,7 @@ internal static class IDWebErrorMessage
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. ";
+ public const string InternalClaimDetected = "IDW10305: The claim '{0}' is reserved for internal use by this library. To ensure proper functionality and avoid conflicts, please remove or rename this claim in your ID Token. ";
// 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.TokenAcquisition/PublicAPI/net462/InternalAPI.Shipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Shipped.txt
index 494ac733b..8f4830af7 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Shipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Shipped.txt
@@ -73,6 +73,7 @@ const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextAndHttpResponseAreNull
const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextIsNull = "IDW10001: HttpContext is null. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.IncorrectNumberOfUriSegments = "IDW10702: Number of URI segments is incorrect: {0}, URI: {1}. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InitializeAsyncIsObsolete = "IDW10801: Use Initialize instead. See https://aka.ms/ms-id-web/1.9.0. " -> string!
+const Microsoft.Identity.Web.IDWebErrorMessage.InternalClaimDetected = "IDW10305: The claim '{0}' is reserved for internal use by this library. To ensure proper functionality and avoid conflicts, please remove or rename this claim in your ID Token. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidAssertion = "IDW10504: Invalid assertion: contains unsupported character(s)." -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidBase64UrlString = "IDW10601: Invalid Base64URL string. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidCertificateStorePath = "IDW10703: Certificate store path must be of the form 'StoreLocation/StoreName'. StoreLocation must be one of 'CurrentUser', 'LocalMachine'. StoreName must be empty or one of '{0}'. " -> string!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Shipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Shipped.txt
index 494ac733b..8f4830af7 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Shipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Shipped.txt
@@ -73,6 +73,7 @@ const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextAndHttpResponseAreNull
const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextIsNull = "IDW10001: HttpContext is null. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.IncorrectNumberOfUriSegments = "IDW10702: Number of URI segments is incorrect: {0}, URI: {1}. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InitializeAsyncIsObsolete = "IDW10801: Use Initialize instead. See https://aka.ms/ms-id-web/1.9.0. " -> string!
+const Microsoft.Identity.Web.IDWebErrorMessage.InternalClaimDetected = "IDW10305: The claim '{0}' is reserved for internal use by this library. To ensure proper functionality and avoid conflicts, please remove or rename this claim in your ID Token. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidAssertion = "IDW10504: Invalid assertion: contains unsupported character(s)." -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidBase64UrlString = "IDW10601: Invalid Base64URL string. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidCertificateStorePath = "IDW10703: Certificate store path must be of the form 'StoreLocation/StoreName'. StoreLocation must be one of 'CurrentUser', 'LocalMachine'. StoreName must be empty or one of '{0}'. " -> string!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Shipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Shipped.txt
index 1b9a83191..e41091f82 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Shipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net6.0/InternalAPI.Shipped.txt
@@ -73,6 +73,7 @@ const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextAndHttpResponseAreNull
const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextIsNull = "IDW10001: HttpContext is null. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.IncorrectNumberOfUriSegments = "IDW10702: Number of URI segments is incorrect: {0}, URI: {1}. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InitializeAsyncIsObsolete = "IDW10801: Use Initialize instead. See https://aka.ms/ms-id-web/1.9.0. " -> string!
+const Microsoft.Identity.Web.IDWebErrorMessage.InternalClaimDetected = "IDW10305: The claim '{0}' is reserved for internal use by this library. To ensure proper functionality and avoid conflicts, please remove or rename this claim in your ID Token. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidAssertion = "IDW10504: Invalid assertion: contains unsupported character(s)." -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidBase64UrlString = "IDW10601: Invalid Base64URL string. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidCertificateStorePath = "IDW10703: Certificate store path must be of the form 'StoreLocation/StoreName'. StoreLocation must be one of 'CurrentUser', 'LocalMachine'. StoreName must be empty or one of '{0}'. " -> string!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Shipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Shipped.txt
index 9b7c49167..9e074ac29 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Shipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net7.0/InternalAPI.Shipped.txt
@@ -73,6 +73,7 @@ const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextAndHttpResponseAreNull
const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextIsNull = "IDW10001: HttpContext is null. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.IncorrectNumberOfUriSegments = "IDW10702: Number of URI segments is incorrect: {0}, URI: {1}. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InitializeAsyncIsObsolete = "IDW10801: Use Initialize instead. See https://aka.ms/ms-id-web/1.9.0. " -> string!
+const Microsoft.Identity.Web.IDWebErrorMessage.InternalClaimDetected = "IDW10305: The claim '{0}' is reserved for internal use by this library. To ensure proper functionality and avoid conflicts, please remove or rename this claim in your ID Token. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidAssertion = "IDW10504: Invalid assertion: contains unsupported character(s)." -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidBase64UrlString = "IDW10601: Invalid Base64URL string. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidCertificateStorePath = "IDW10703: Certificate store path must be of the form 'StoreLocation/StoreName'. StoreLocation must be one of 'CurrentUser', 'LocalMachine'. StoreName must be empty or one of '{0}'. " -> string!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Shipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Shipped.txt
index 9b7c49167..9e074ac29 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Shipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Shipped.txt
@@ -73,6 +73,7 @@ const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextAndHttpResponseAreNull
const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextIsNull = "IDW10001: HttpContext is null. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.IncorrectNumberOfUriSegments = "IDW10702: Number of URI segments is incorrect: {0}, URI: {1}. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InitializeAsyncIsObsolete = "IDW10801: Use Initialize instead. See https://aka.ms/ms-id-web/1.9.0. " -> string!
+const Microsoft.Identity.Web.IDWebErrorMessage.InternalClaimDetected = "IDW10305: The claim '{0}' is reserved for internal use by this library. To ensure proper functionality and avoid conflicts, please remove or rename this claim in your ID Token. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidAssertion = "IDW10504: Invalid assertion: contains unsupported character(s)." -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidBase64UrlString = "IDW10601: Invalid Base64URL string. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidCertificateStorePath = "IDW10703: Certificate store path must be of the form 'StoreLocation/StoreName'. StoreLocation must be one of 'CurrentUser', 'LocalMachine'. StoreName must be empty or one of '{0}'. " -> string!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Shipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Shipped.txt
index 9b7c49167..9e074ac29 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Shipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Shipped.txt
@@ -73,6 +73,7 @@ const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextAndHttpResponseAreNull
const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextIsNull = "IDW10001: HttpContext is null. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.IncorrectNumberOfUriSegments = "IDW10702: Number of URI segments is incorrect: {0}, URI: {1}. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InitializeAsyncIsObsolete = "IDW10801: Use Initialize instead. See https://aka.ms/ms-id-web/1.9.0. " -> string!
+const Microsoft.Identity.Web.IDWebErrorMessage.InternalClaimDetected = "IDW10305: The claim '{0}' is reserved for internal use by this library. To ensure proper functionality and avoid conflicts, please remove or rename this claim in your ID Token. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidAssertion = "IDW10504: Invalid assertion: contains unsupported character(s)." -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidBase64UrlString = "IDW10601: Invalid Base64URL string. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidCertificateStorePath = "IDW10703: Certificate store path must be of the form 'StoreLocation/StoreName'. StoreLocation must be one of 'CurrentUser', 'LocalMachine'. StoreName must be empty or one of '{0}'. " -> string!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Shipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Shipped.txt
index 494ac733b..8f4830af7 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Shipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Shipped.txt
@@ -73,6 +73,7 @@ const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextAndHttpResponseAreNull
const Microsoft.Identity.Web.IDWebErrorMessage.HttpContextIsNull = "IDW10001: HttpContext is null. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.IncorrectNumberOfUriSegments = "IDW10702: Number of URI segments is incorrect: {0}, URI: {1}. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InitializeAsyncIsObsolete = "IDW10801: Use Initialize instead. See https://aka.ms/ms-id-web/1.9.0. " -> string!
+const Microsoft.Identity.Web.IDWebErrorMessage.InternalClaimDetected = "IDW10305: The claim '{0}' is reserved for internal use by this library. To ensure proper functionality and avoid conflicts, please remove or rename this claim in your ID Token. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidAssertion = "IDW10504: Invalid assertion: contains unsupported character(s)." -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidBase64UrlString = "IDW10601: Invalid Base64URL string. " -> string!
const Microsoft.Identity.Web.IDWebErrorMessage.InvalidCertificateStorePath = "IDW10703: Certificate store path must be of the form 'StoreLocation/StoreName'. StoreLocation must be one of 'CurrentUser', 'LocalMachine'. StoreName must be empty or one of '{0}'. " -> string!
diff --git a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilder.cs b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilder.cs
index d43edc207..dafd1d250 100644
--- a/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilder.cs
+++ b/src/Microsoft.Identity.Web/WebAppExtensions/MicrosoftIdentityWebAppAuthenticationBuilder.cs
@@ -4,7 +4,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
using System.Linq;
+using System.Security.Authentication;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
@@ -177,10 +179,37 @@ internal static void WebAppCallsWebApiImplementation(
{
ClientInfo? clientInfoFromServer = ClientInfo.CreateFromJson(clientInfo);
- if (clientInfoFromServer != null && clientInfoFromServer.UniqueTenantIdentifier != null && clientInfoFromServer.UniqueObjectIdentifier != null)
+ if (clientInfoFromServer != null)
{
- context!.Principal!.Identities.FirstOrDefault()?.AddClaim(new Claim(ClaimConstants.UniqueTenantIdentifier, clientInfoFromServer.UniqueTenantIdentifier));
- context!.Principal!.Identities.FirstOrDefault()?.AddClaim(new Claim(ClaimConstants.UniqueObjectIdentifier, clientInfoFromServer.UniqueObjectIdentifier));
+ var identity = context!.Principal!.Identities.FirstOrDefault();
+ if (identity != null)
+ {
+ if (clientInfoFromServer.UniqueTenantIdentifier != null)
+ {
+ var uniqueTenantIdentifierClaim = identity.FindFirst(c => c.Type == ClaimConstants.UniqueTenantIdentifier);
+ if (uniqueTenantIdentifierClaim != null && !string.Equals(clientInfoFromServer.UniqueTenantIdentifier, uniqueTenantIdentifierClaim.Value, StringComparison.OrdinalIgnoreCase))
+ {
+ context.Fail(new AuthenticationException(string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.InternalClaimDetected, ClaimConstants.UniqueTenantIdentifier)));
+ return;
+ }
+ }
+
+ if (clientInfoFromServer.UniqueObjectIdentifier != null)
+ {
+ var uniqueObjectIdentifierClaim = identity.FindFirst(c => c.Type == ClaimConstants.UniqueObjectIdentifier);
+ if (uniqueObjectIdentifierClaim != null && !string.Equals(clientInfoFromServer.UniqueObjectIdentifier, uniqueObjectIdentifierClaim.Value, StringComparison.OrdinalIgnoreCase))
+ {
+ context.Fail(new AuthenticationException(string.Format(CultureInfo.InvariantCulture, IDWebErrorMessage.InternalClaimDetected, ClaimConstants.UniqueObjectIdentifier)));
+ return;
+ }
+ }
+
+ if (clientInfoFromServer.UniqueTenantIdentifier != null && clientInfoFromServer.UniqueObjectIdentifier != null)
+ {
+ identity.AddClaim(new Claim(ClaimConstants.UniqueTenantIdentifier, clientInfoFromServer.UniqueTenantIdentifier));
+ identity.AddClaim(new Claim(ClaimConstants.UniqueObjectIdentifier, clientInfoFromServer.UniqueObjectIdentifier));
+ }
+ }
}
}
await onTokenValidatedHandler(context).ConfigureAwait(false);
diff --git a/tests/Microsoft.Identity.Web.Test.Common/TestHelpers/HttpContextUtilities.cs b/tests/Microsoft.Identity.Web.Test.Common/TestHelpers/HttpContextUtilities.cs
index 7c64e72ab..87cf55e06 100644
--- a/tests/Microsoft.Identity.Web.Test.Common/TestHelpers/HttpContextUtilities.cs
+++ b/tests/Microsoft.Identity.Web.Test.Common/TestHelpers/HttpContextUtilities.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+using System.Collections.Generic;
using System.IO;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
@@ -44,5 +45,14 @@ public static HttpContext CreateHttpContext(
return httpContext;
}
+
+ public static HttpContext CreateHttpContext(IEnumerable claims)
+ {
+ var httpContext = CreateHttpContext();
+
+ httpContext.User = new ClaimsPrincipal(new CaseSensitiveClaimsIdentity(claims));
+
+ return httpContext;
+ }
}
}
diff --git a/tests/Microsoft.Identity.Web.Test/WebAppExtensionsTests.cs b/tests/Microsoft.Identity.Web.Test/WebAppExtensionsTests.cs
index 3a5cdc8c9..7f89c0941 100644
--- a/tests/Microsoft.Identity.Web.Test/WebAppExtensionsTests.cs
+++ b/tests/Microsoft.Identity.Web.Test/WebAppExtensionsTests.cs
@@ -20,7 +20,6 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.Internal;
-using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Graph;
using Microsoft.Identity.Client;
@@ -360,6 +359,110 @@ public async Task AddMicrosoftIdentityWebAppCallsWebApi_WithConfigNameParameters
await AddMicrosoftIdentityWebAppCallsWebApi_TestRedirectToIdentityProviderForSignOutEventAsync(provider, oidcOptions, redirectFuncMock, tokenAcquisitionMock);
}
+ [Theory]
+ [InlineData(ClaimConstants.UniqueObjectIdentifier, "user-uid")]
+ [InlineData(ClaimConstants.UniqueTenantIdentifier, "user-utid")]
+ public async Task AddMicrosoftIdentityWebAppCallsWebApi_WithConfigNameParametersAsync_ShouldThrowExceptionForInternalClaims_WhenClaimsDiffer(string claimType, string claimValue)
+ {
+ var configMock = Substitute.For();
+ configMock.Configure().GetSection(ConfigSectionName).Returns(_configSection);
+ var initialScopes = new List() { "custom_scope" };
+ var tokenAcquisitionMock = Substitute.For();
+ var authCodeReceivedFuncMock = Substitute.For>();
+ var tokenValidatedFuncMock = Substitute.For>();
+ var redirectFuncMock = Substitute.For>();
+ var services = new ServiceCollection();
+
+ services.AddSingleton((provider) => _env)
+ .AddSingleton(configMock);
+
+ services.AddAuthentication()
+ .AddMicrosoftIdentityWebApp(configMock, ConfigSectionName, OidcScheme)
+ .EnableTokenAcquisitionToCallDownstreamApi(initialScopes);
+ services.Configure(OidcScheme, (options) =>
+ {
+ options.Events ??= new OpenIdConnectEvents();
+ options.Events.OnAuthorizationCodeReceived += authCodeReceivedFuncMock;
+ options.Events.OnTokenValidated += tokenValidatedFuncMock;
+ options.Events.OnRedirectToIdentityProviderForSignOut += redirectFuncMock;
+ });
+
+ services.RemoveAll();
+ services.AddScoped((provider) => tokenAcquisitionMock);
+
+ var provider = services.BuildServiceProvider();
+
+ // Assert config bind actions added correctly
+ provider.GetRequiredService>().Get(OidcScheme);
+ provider.GetRequiredService>().Get(OidcScheme);
+
+ configMock.Received(1).GetSection(ConfigSectionName);
+
+ var oidcOptions = provider.GetRequiredService>().Get(OidcScheme);
+
+ AddMicrosoftIdentityWebAppCallsWebApi_TestCommon(services, provider, oidcOptions, initialScopes);
+ await AddMicrosoftIdentityWebAppCallsWebApi_TestAuthorizationCodeReceivedEventAsync(provider, oidcOptions, authCodeReceivedFuncMock, tokenAcquisitionMock);
+ await AddMicrosoftIdentityWebAppCallsWebApi_TestTokenValidatedEventAsync(provider, oidcOptions, new Claim[] { new Claim(claimType, claimValue) },
+ tokenValidatedContext =>
+ {
+ Assert.False(tokenValidatedContext.Result.Succeeded);
+ Assert.NotNull(tokenValidatedContext.Result.Failure);
+ Assert.IsType(tokenValidatedContext.Result.Failure);
+ });
+ await AddMicrosoftIdentityWebAppCallsWebApi_TestRedirectToIdentityProviderForSignOutEventAsync(provider, oidcOptions, redirectFuncMock, tokenAcquisitionMock);
+ }
+
+ [Theory]
+ [InlineData(ClaimConstants.UniqueObjectIdentifier, TestConstants.Uid)]
+ [InlineData(ClaimConstants.UniqueTenantIdentifier, TestConstants.Utid)]
+ public async Task AddMicrosoftIdentityWebAppCallsWebApi_WithConfigNameParametersAsync_ShouldNotThrowExceptionForInternalClaims_WhenClaimsAreEqual(string claimType, string claimValue)
+ {
+ var configMock = Substitute.For();
+ configMock.Configure().GetSection(ConfigSectionName).Returns(_configSection);
+ var initialScopes = new List() { "custom_scope" };
+ var tokenAcquisitionMock = Substitute.For();
+ var authCodeReceivedFuncMock = Substitute.For>();
+ var tokenValidatedFuncMock = Substitute.For>();
+ var redirectFuncMock = Substitute.For>();
+ var services = new ServiceCollection();
+
+ services.AddSingleton((provider) => _env)
+ .AddSingleton(configMock);
+
+ services.AddAuthentication()
+ .AddMicrosoftIdentityWebApp(configMock, ConfigSectionName, OidcScheme)
+ .EnableTokenAcquisitionToCallDownstreamApi(initialScopes);
+ services.Configure(OidcScheme, (options) =>
+ {
+ options.Events ??= new OpenIdConnectEvents();
+ options.Events.OnAuthorizationCodeReceived += authCodeReceivedFuncMock;
+ options.Events.OnTokenValidated += tokenValidatedFuncMock;
+ options.Events.OnRedirectToIdentityProviderForSignOut += redirectFuncMock;
+ });
+
+ services.RemoveAll();
+ services.AddScoped((provider) => tokenAcquisitionMock);
+
+ var provider = services.BuildServiceProvider();
+
+ // Assert config bind actions added correctly
+ provider.GetRequiredService>().Get(OidcScheme);
+ provider.GetRequiredService>().Get(OidcScheme);
+
+ configMock.Received(1).GetSection(ConfigSectionName);
+
+ var oidcOptions = provider.GetRequiredService>().Get(OidcScheme);
+
+ AddMicrosoftIdentityWebAppCallsWebApi_TestCommon(services, provider, oidcOptions, initialScopes);
+ await AddMicrosoftIdentityWebAppCallsWebApi_TestAuthorizationCodeReceivedEventAsync(provider, oidcOptions, authCodeReceivedFuncMock, tokenAcquisitionMock);
+ await AddMicrosoftIdentityWebAppCallsWebApi_TestTokenValidatedEventAsync(provider, oidcOptions, new Claim[] { new Claim(claimType, claimValue) },
+ tokenValidatedContext =>
+ {
+ Assert.Null(tokenValidatedContext.Result);
+ });
+ await AddMicrosoftIdentityWebAppCallsWebApi_TestRedirectToIdentityProviderForSignOutEventAsync(provider, oidcOptions, redirectFuncMock, tokenAcquisitionMock);
+ }
+
[Fact]
public async Task AddMicrosoftIdentityWebAppCallsWebApi_WithConfigActionParametersAsync()
{
@@ -405,6 +508,110 @@ public async Task AddMicrosoftIdentityWebAppCallsWebApi_WithConfigActionParamete
await AddMicrosoftIdentityWebAppCallsWebApi_TestRedirectToIdentityProviderForSignOutEventAsync(provider, oidcOptions, redirectFuncMock, tokenAcquisitionMock);
}
+ [Theory]
+ [InlineData(ClaimConstants.UniqueObjectIdentifier, "user-uid")]
+ [InlineData(ClaimConstants.UniqueTenantIdentifier, "user-utid")]
+ public async Task AddMicrosoftIdentityWebAppCallsWebApi_WithConfigActionParametersAsync_ShouldThrowExceptionForInternalClaims_WhenClaimsDiffer(string claimType, string claimValue)
+ {
+ var configMock = Substitute.For();
+ var initialScopes = new List() { "custom_scope" };
+ var tokenAcquisitionMock = Substitute.For();
+ var authCodeReceivedFuncMock = Substitute.For>();
+ var tokenValidatedFuncMock = Substitute.For>();
+ var redirectFuncMock = Substitute.For>();
+
+ var services = new ServiceCollection();
+ services.AddSingleton(configMock);
+ services.AddSingleton((provider) => _env);
+
+ var builder = services.AddAuthentication()
+ .AddMicrosoftIdentityWebApp(_configureMsOptions, null, OidcScheme)
+ .EnableTokenAcquisitionToCallDownstreamApi(_configureAppOptions, initialScopes);
+ services.Configure(OidcScheme, (options) =>
+ {
+ options.Events ??= new OpenIdConnectEvents();
+ options.Events.OnAuthorizationCodeReceived += authCodeReceivedFuncMock;
+ options.Events.OnTokenValidated += tokenValidatedFuncMock;
+ options.Events.OnRedirectToIdentityProviderForSignOut += redirectFuncMock;
+ });
+
+ services.RemoveAll();
+ services.AddScoped((provider) => tokenAcquisitionMock);
+
+ var provider = builder.Services.BuildServiceProvider();
+
+ // Assert configure options actions added correctly
+ var configuredAppOptions = provider.GetServices>().Cast>();
+ var configuredMsOptions = provider.GetServices>().Cast>();
+
+ Assert.Contains(configuredAppOptions, o => o.Action == _configureAppOptions);
+ Assert.Contains(configuredMsOptions, o => o.Action == _configureMsOptions);
+
+ var oidcOptions = provider.GetRequiredService>().Create(OidcScheme);
+
+ AddMicrosoftIdentityWebAppCallsWebApi_TestCommon(services, provider, oidcOptions, initialScopes);
+ await AddMicrosoftIdentityWebAppCallsWebApi_TestAuthorizationCodeReceivedEventAsync(provider, oidcOptions, authCodeReceivedFuncMock, tokenAcquisitionMock);
+ await AddMicrosoftIdentityWebAppCallsWebApi_TestTokenValidatedEventAsync(provider, oidcOptions, new Claim[] { new Claim(claimType, claimValue) },
+ tokenValidatedContext =>
+ {
+ Assert.False(tokenValidatedContext.Result.Succeeded);
+ Assert.NotNull(tokenValidatedContext.Result.Failure);
+ Assert.IsType(tokenValidatedContext.Result.Failure);
+ });
+ await AddMicrosoftIdentityWebAppCallsWebApi_TestRedirectToIdentityProviderForSignOutEventAsync(provider, oidcOptions, redirectFuncMock, tokenAcquisitionMock);
+ }
+
+ [Theory]
+ [InlineData(ClaimConstants.UniqueObjectIdentifier, TestConstants.Uid)]
+ [InlineData(ClaimConstants.UniqueTenantIdentifier, TestConstants.Utid)]
+ public async Task AddMicrosoftIdentityWebAppCallsWebApi_WithConfigActionParametersAsync_ShouldNotThrowExceptionForInternalClaims_WhenClaimsAreEqual(string claimType, string claimValue)
+ {
+ var configMock = Substitute.For();
+ var initialScopes = new List() { "custom_scope" };
+ var tokenAcquisitionMock = Substitute.For();
+ var authCodeReceivedFuncMock = Substitute.For>();
+ var tokenValidatedFuncMock = Substitute.For>();
+ var redirectFuncMock = Substitute.For>();
+
+ var services = new ServiceCollection();
+ services.AddSingleton(configMock);
+ services.AddSingleton((provider) => _env);
+
+ var builder = services.AddAuthentication()
+ .AddMicrosoftIdentityWebApp(_configureMsOptions, null, OidcScheme)
+ .EnableTokenAcquisitionToCallDownstreamApi(_configureAppOptions, initialScopes);
+ services.Configure(OidcScheme, (options) =>
+ {
+ options.Events ??= new OpenIdConnectEvents();
+ options.Events.OnAuthorizationCodeReceived += authCodeReceivedFuncMock;
+ options.Events.OnTokenValidated += tokenValidatedFuncMock;
+ options.Events.OnRedirectToIdentityProviderForSignOut += redirectFuncMock;
+ });
+
+ services.RemoveAll();
+ services.AddScoped((provider) => tokenAcquisitionMock);
+
+ var provider = builder.Services.BuildServiceProvider();
+
+ // Assert configure options actions added correctly
+ var configuredAppOptions = provider.GetServices>().Cast>();
+ var configuredMsOptions = provider.GetServices>().Cast>();
+
+ Assert.Contains(configuredAppOptions, o => o.Action == _configureAppOptions);
+ Assert.Contains(configuredMsOptions, o => o.Action == _configureMsOptions);
+
+ var oidcOptions = provider.GetRequiredService>().Create(OidcScheme);
+
+ AddMicrosoftIdentityWebAppCallsWebApi_TestCommon(services, provider, oidcOptions, initialScopes);
+ await AddMicrosoftIdentityWebAppCallsWebApi_TestAuthorizationCodeReceivedEventAsync(provider, oidcOptions, authCodeReceivedFuncMock, tokenAcquisitionMock);
+ await AddMicrosoftIdentityWebAppCallsWebApi_TestTokenValidatedEventAsync(provider, oidcOptions, new Claim[] { new Claim(claimType, claimValue) },
+ tokenValidatedContext =>
+ {
+ Assert.Null(tokenValidatedContext.Result);
+ });
+ await AddMicrosoftIdentityWebAppCallsWebApi_TestRedirectToIdentityProviderForSignOutEventAsync(provider, oidcOptions, redirectFuncMock, tokenAcquisitionMock);
+ }
+
[Fact]
public void AddMicrosoftIdentityWebAppCallsWebApi_NoScopes()
{
@@ -853,6 +1060,23 @@ private async Task AddMicrosoftIdentityWebAppCallsWebApi_TestTokenValidatedEvent
Assert.True(tokenValidatedContext?.Principal?.HasClaim(c => c.Type == ClaimConstants.UniqueObjectIdentifier));
}
+ private async Task AddMicrosoftIdentityWebAppCallsWebApi_TestTokenValidatedEventAsync(IServiceProvider provider, OpenIdConnectOptions oidcOptions, IEnumerable? claims, Action assertions)
+ {
+ var (httpContext, authScheme, authProperties) = CreateContextParameters(provider, claims);
+
+ var tokenValidatedContext = new TokenValidatedContext(httpContext, authScheme, oidcOptions, httpContext.User, authProperties)
+ {
+ ProtocolMessage = new OpenIdConnectMessage(
+ new Dictionary()
+ {
+ { ClaimConstants.ClientInfo, new string[] { Base64UrlHelpers.Encode($"{{\"uid\":\"{TestConstants.Uid}\",\"utid\":\"{TestConstants.Utid}\"}}")! } },
+ }),
+ };
+
+ await oidcOptions.Events.TokenValidated(tokenValidatedContext);
+ assertions(tokenValidatedContext);
+ }
+
private async Task AddMicrosoftIdentityWebAppCallsWebApi_TestRedirectToIdentityProviderForSignOutEventAsync(
IServiceProvider provider,
OpenIdConnectOptions oidcOptions,
@@ -868,9 +1092,9 @@ private async Task AddMicrosoftIdentityWebAppCallsWebApi_TestRedirectToIdentityP
await tokenAcquisitionMock.ReceivedWithAnyArgs().RemoveAccountAsync(Arg.Any());
}
- private (HttpContext, AuthenticationScheme, AuthenticationProperties) CreateContextParameters(IServiceProvider provider)
+ private (HttpContext, AuthenticationScheme, AuthenticationProperties) CreateContextParameters(IServiceProvider provider, IEnumerable? claims = null)
{
- var httpContext = HttpContextUtilities.CreateHttpContext();
+ var httpContext = claims != null ? HttpContextUtilities.CreateHttpContext(claims) : HttpContextUtilities.CreateHttpContext();
httpContext.RequestServices = provider;
var authScheme = new AuthenticationScheme(OpenIdConnectDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme, typeof(OpenIdConnectHandler));