I am in the process of upgrading an app from Azure AD B2C to Entra ID for customers. My understanding is that the general configuration details are the same as those for a single-org Azure AD app. The closest I've gotten to the issue is this error:
Message: IDX40001: Issuer: 'https://{tenantGuid}.ciamlogin.com/{tenantGuid}/', does not match any of the valid issuers provided for this application. ,
InnerException: IDX20803: Unable to obtain configuration from: 'https://{tenantDomain}.ciamlogin.com/{tenantGuid}/.well-known/openid-configuration'. Will retry at 'xxxx'.
Exception: 'System.IO.IOException: IDX20804: Unable to retrieve document from: 'https://{tenantDomain}.ciamlogin.com/common/discovery/keys'.
Strangely enough, from the client side the returned Www-authentication header reads
Bearer error="invalid_token", error_description="The issuer '(null)' is invalid"
What's crazy to me is that if I press Ctrl+Shift+R a few times (usually between 3 and 10), this issue sometimes resolves itself. From the error above, it seems to me that Microsoft.Identity.Web is looking in the wrong place for the keys for this token, since it's using the /common/ endpoint rather than the tenant ID specified in appsettings. I can only presume that it starts looking in the right place after a bit, but have no idea what could cause that. Restarts of the app and fresh instances of a private browser seem to produce random results.
I've confirmed that API requests I'm sending to the .NET app include the exact same bearer token for all requests in a session, and that that token includes an iss claim that looks like below. One request with the same token may fail, while the next may work.
"iss": "https://{tenantGuid}.ciamlogin.com/{tenantGuid}/"
Here's the relevant appsettings.development.json:
"AzureAd": {
"Instance": "https://{tenantDomain}.ciamlogin.com/",
"ClientId": "{serverAppRegistrationClientId}",
"Domain": "{tenantDomain}.onmicrosoft.com",
"TenantId": "{tenantGuid}",
"CallbackPath": "/signin-oidc",
"Scopes": {
"Read": [ "{scope1}.Read", "{scope1}.ReadWrite" ],
"Write": [ "{scope1}.ReadWrite" ]
}
},
Relevant program.cs (I used CleanArchitecture as my project template)
var builder = WebApplication.CreateBuilder(args);
// This is required to be instantiated before the OpenIdConnectOptions starts getting configured.
// By default, the claims mapping will map claim names in the old format to accommodate older SAML applications.
// For instance, 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role' instead of 'roles' claim.
// This flag ensures that the ClaimsIdentity claims collection will be built from the claims in the token
JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration, "AzureAd", OpenIdConnectDefaults.AuthenticationScheme);
builder.Services.AddAuthorizationBuilder()...
// The following flag can be used to get more descriptive errors in development environments
IdentityModelEventSource.ShowPII = true;
builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssemblyContaining();
});
builder.Services.AddApplicationServices(builder.Configuration);
builder.Services.AddInfrastructureServices(builder.Configuration);
builder.Services.AddWebServices();
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<{entity1}>("{Entity1}");
builder.Services.AddControllers().AddOData(options =>
options.EnableQueryFeatures().AddRouteComponents(
routePrefix: "api",
model: modelBuilder.GetEdmModel())
);
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error");
app.UseMigrationsEndPoint();
await app.InitialiseDatabaseAsync();
}
else
{
app.UseExceptionHandler("/error");
app.UseHsts();
}
// Enables the swagger stuff
app.UseSwagger();
app.UseSwaggerUI(options =>
{
//options.DefaultModelRendering(ModelRendering.Example);
options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
//options.DefaultModelExpandDepth(0);
//options.DefaultModelsExpandDepth(-1);
});
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseODataRouteDebug();
app.MapControllers();
app.MapControllerRoute(
name: "default",
pattern: "api/{controller}/{action=Index}/{id?}");
app.MapFallbackToFile("index.html");
app.Run();
public partial class Program { }
Relevant auth-config.ts:
import { LogLevel, Configuration, BrowserCacheLocation } from '@azure/msal-browser';
const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.navigator.userAgent.indexOf("Trident/") > -1;
export const msalConfig: Configuration = {
auth: {
clientId: '{clientAppRegistrationClientId}',
authority: 'https://{tenantDomain}.ciamlogin.com',
redirectUri: '/signin-oidc',
postLogoutRedirectUri: '/',
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE,
},
system: {
loggerOptions: {
loggerCallback(logLevel: LogLevel, message: string) {
console.log(message);
},
logLevel: LogLevel.Verbose,
piiLoggingEnabled: false
}
}
}
export const protectedResources = {
apiScope1: {
endpoint: "/api/entity1",
scopes: {
read: ["api://{serverAppRegistrationClientId}/{scope1}.Read"],
write: ["api://{serverAppRegistrationClientId}/{scope1}.ReadWrite"]
}
}
}
export const loginRequest = {
scopes: []
};
Relevant dependencies:
- .NET 8.0.1
- Microsoft.Identity.Web 2.17.4
- @azure/msal-angular 3.0.9
- @azure/msal-browser 3.6.0
I've tried:
Setting up a new Entra for customer's tenant
Trying various combinations of
auth-config.jsonandappsettings.development.json, although I wish I'd have logged what I did at the time. It mostly revolved around switching{tenantDomain}with{tenantGuid}, and changing authority inauth-config.tsor instance inappsettingsto follow this syntax:https://{tenantGuid}.ciamlogin.com/{tenantGuid}/v2.0I've observed that MSAL.js hits the v2.0 endpoint by default, which returns a v1.0 access token and a v2.0 Id token.
Reading MS guide on setting up MSAL for angular with Entra ID for Customers, and copying the configuration from there and the Woodgrove Demo to no success.
Trying
JwtSecurityTokenHandler.DefaultMapInboundClaims = false, which made it to where I would get 403 instead of 200 when I got past 401. The 401's would still occur though.Trying both
OpenIdConnectDefaults.AuthenticationSchemeandJwtBearerDefaults.AuthenticationSchemeforAddAuthentication()