IDX40001 raised when using multiple Authentication Schemes
Microsoft.Identity.Web Library
Microsoft.Identity.Web
Microsoft.Identity.Web version
2.16.1
Web app
Sign-in users and call web APIs
Web API
Protected web APIs call downstream web APIs
Token cache serialization
In-memory caches
Description
ASPNET Core Web API using .NET8 We have multiple controllers, some using an Azure Ad authentication scheme and some Azure Ad B2C. This is not an new issue for us but one that I have rercently removed the hack for hoping that it would be no longer needed.
If we attempt to configure both Authentication Schemes
services
.AddMicrosoftIdentityWebApiAuthentication(this.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
services.AddAuthentication()
.AddMicrosoftIdentityWebApi(this.Configuration, "AzureAdB2C", "B2CScheme", true);
We will get a warning written to the logs whenver the client app (secured with Azure Ad B2C) call the app service
IDX40001: Issuer: 'https://ourTenanatName.b2clogin.com/ourTenantId/v2.0/', does not match any of the valid issuers provided for this application.
This is an incorrect statement. That is a configured issuer. Furthermore if I remove the Azure Ad configuration
services
.AddMicrosoftIdentityWebApiAuthentication(this.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
The warning goes away. Obviously I cannot do that as this Api needs to support Azure Ad consumers.
I have tried directly setting the ValidIssuers array but that does not stop the warning
services.AddAuthentication()
.AddMicrosoftIdentityWebApi(
jwtBearerOptions =>
{
this.Configuration.Bind("AzureAdB2C", jwtBearerOptions);
jwtBearerOptions.TokenValidationParameters.ValidIssuers = new List<string>
{
"https://ourDevTenantName.b2clogin.com/DevTenantId/v2.0/",
"https://outTestTenantName.b2clogin.com/TesttenantId/v2.0/"
// Add other valid issuers here
};
},
microsoftIdentityOptions =>
{
this.Configuration.Bind("AzureAdB2C", microsoftIdentityOptions);
},
"B2CScheme",
true);
Our previous hack was use a third fake scheme and a ForwardDefeaultSelector
services.AddAuthentication("AzureAD_OR_AzureAdB2C")
.AddMicrosoftIdentityWebApi(
jwtBearerOptions =>
{
jwtBearerOptions.ForwardDefaultSelector = this.GetDefaultAuthenticationSchemeFromToken;
},
identityOptions =>
{
var azureAdB2CConfig = this.Configuration.GetSection("AzureAdB2C");
identityOptions.Instance = azureAdB2CConfig.GetValue<string>("Instance");
identityOptions.TenantId = "AzureAD_OR_AzureAdB2C";
identityOptions.ClientId = "AzureAD_OR_AzureAdB2C";
},
"AzureAD_OR_AzureAdB2C",
false);
I am trying to remove any hacks.
Surely what we are trying to do is relatively normal and should not cause these issues
Reproduction steps
Configure both Azure Ad and Azure Ad B2C schemes
services
.AddMicrosoftIdentityWebApiAuthentication(this.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
services.AddAuthentication()
.AddMicrosoftIdentityWebApi(this.Configuration, "AzureAdB2C", "B2CScheme", true);
Error message
IDX40001: Issuer: 'https://ourTenanatName.b2clogin.com/ourTenantId/v2.0/', does not match any of the valid issuers provided for this application
Id Web logs
No response
Relevant code snippets
[HttpPut]
[Authorize(AuthenticationSchemes = "B2CScheme")]
[Route("installations")]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.UnprocessableEntity)]
public async Task<IActionResult> UpdateInstallation([Required] DeviceIdentity deviceIdentity)
{
IncludeUserIdAsATag(this.ControllerContext.HttpContext.User, deviceIdentity);
IncludeClinicsAsTags(this.ControllerContext.HttpContext.User, deviceIdentity);
if (string.IsNullOrWhiteSpace(deviceIdentity.InstallationId) ||
string.IsNullOrWhiteSpace(deviceIdentity.Platform) ||
string.IsNullOrWhiteSpace(deviceIdentity.PushChannel))
{
this.logger.LogWarning("UpdateInstallation: Body missing mandatory values");
return new UnprocessableEntityObjectResult("Body missing mandatory values");
}
if (notificationService.IsPlatformSupported(deviceIdentity.Platform) == false)
{
this.logger.LogWarning("UpdateInstallation: Platform {DevicePlatform} not supported", deviceIdentity.Platform);
return new UnprocessableEntityObjectResult("Platform not supported");
}
var errorMessage = await this.notificationService.CreateOrUpdateInstallationAsync(deviceIdentity, HttpContext.RequestAborted);
if (errorMessage != null)
{
return new ObjectResult(errorMessage)
{
StatusCode = (int)HttpStatusCode.InternalServerError,
};
}
else
{
return new OkResult();
}
}
Regression
No response
Expected behavior
I would expect the Warning message to not be written to the logs.
I am getting the same issue. When I comment either Azure Ad or B2C configuration, the application accepts token from the clients in that uncommented tenant. I am using .NET 8. Any suggestions would be great help!
Hi @munkii , Would you mind sharing the code for the hack GetDefaultAuthenticationSchemeFromToken . I am planning to implement something similar until we have a permanent fix. Thanks
Sorry for the delay in replying...
jwtBearerOptions.ForwardDefaultSelector = context =>
{
var token = string.Empty;
if (context.Request.Headers.TryGetValue("Authorization", out var value))
{
string authorization = value;
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}
}
if (token == null)
{
this.logger.LogInformation($"Cannot get the Token out of the Authorization header");
}
var jwtHandler = new JwtSecurityTokenHandler();
if (jwtHandler.CanReadToken(token))
{
var jwtToken = jwtHandler.ReadJwtToken(token);
var expectedB2CIssuer = $"{azureAdB2CConfig.GetValue<string>("Instance")}/{azureAdB2CConfig.GetValue<string>("TenantId")}/v2.0/";
if (string.Compare(jwtToken.Issuer, expectedB2CIssuer, true) == 0)
{
// Claim is from B2C so this request should be validated against the B2C scheme.
this.logger.LogInformation($"Request is with a B2C issued token so refer to B2CScheme. Token issuer: {jwtToken.Issuer} B2C Issuer: {expectedB2CIssuer}");
return "B2CScheme";
}
else
{
this.logger.LogInformation($"Request is not with a B2C issued token so refer to Bearer scheme. Token issuer: {jwtToken.Issuer} B2C Issuer: {expectedB2CIssuer}");
}
}
else
{
this.logger.LogInformation("Request token could not be read so refer to Bearer scheme");
}
return "Bearer";
};
Hi, I have been struggling with this for a while myself and thanks to your code stumbled upon this documentation from Microsoft: https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?view=aspnetcore-8.0#use-multiple-authentication-schemes
Here they do something very similar, except using AddPolicyScheme instead of AddMicrosoftIdentityWebApi, allowing you to specify the ForwardDefaultSelector without having to pass foobar arguments for the Azure AD (B2C) configuration. So it seems the 'hack' is actually the correct way to implement support for multiple authentication schemes.
It seems pretty dumb to me that this “hack” is how it is supposed to work.
@westin-m - can we add a configuration switch, or something in the appsettings.json, which developers can turn on if they want the full logs from IdentityModel, otherwise, we don't let IdentityModel log, only the logs from aspnetcore.
I have this issue but is just being logged as trace.
If I understand, it is basically getting the correct policy as you mentioned in ForwardDefaultSelector, however when you set [Authorize(AuthenticationSchemes = "B2CScheme")], it will try to validate the issuer for "B2CScheme" using "Bearer" then, it logs the trace.
IDX40001: Issuer: 'https://{domain}.b2clogin.com/{ourTenantId}/v2.0/', does not match any of the valid issuers provided for this application.
In my case, to fix it, I just have to remove the schemas from Authorize attribute and works well. In your case, you cannot. My question is, why it is switching the validations?
#2816 enables turning off logs from IdWeb/ using a null logger for the same effect.