When B2C is detected (presence of a policy) but the authority is login.microsoftonline.com without the tfp, we should log an error and throw an exception ArgumentException in AddMicrosoftWebApp(), so that customers know that they either have to use login.b2c.com or have tfp.
Error message:
cc: @jennyf19
Would https://github.com/AzureAD/microsoft-identity-web/issues/168 be a duplicate?
Microsoft.Identity.Web nuget package (version 0.1.1-preview)
> dotnet --version3.1.201> git clone https://github.com/AzureAD/microsoft-identity-web.git> cd microsoft-identity-web/ProjectTemplates> dotnet pack AspNetCoreMicrosoftIdentityWebProjectTemplates.csproj> cd bin/Debug> dotnet new --install Microsoft.Identity.Web.ProjectTemplates.0.1.0.nupkgVerify if the templates are available
> dotnet new
Now, let's create a new ASP.NET Core web api project
> dotnet new webapi2 --auth Singleorg -n WeatherStation> cd WeatherStationappsettings.json file (image has fake data)
> dotnet run
> curl -i http://localhost:5000/weatherforecast -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI2NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNzNiOWUwZjgtNDY1Yy00MzY1LTkzNGUtMTM4ZmUxYjEyNDQ4L3YyLjAvIiwiZXhwIjoxNTg4NjQ2NTIyLCJuYmYi9wbWVudCIsImZhbWlseV9uYW1lIjoiR3JvdXAiLCJuYW1lIjoiRGV2ZWxvcG1lbnQgR3JvdXAiLCJuZXdVc3VyIjpmYWxzZSwiZXh0ZW5zaW9uX0NvbXBhbnkiOiJNSUNST0RFQSIsImV4dGVuc2lvbl9QaG9uZSI6IjU1NTU1NTU1NTUiLCJlbWFpbHMiOlsiZGV2ZWxvcG1lbnRncm91cEBtaWNyb2RlYS5jb20iXSwidGZwIjoiQjJDXzFfTG9hZFBhbERldiIsImF6cCI6IjliNzBjZTY4LTZlOWEtNGJiZC1iYWU3LWVhZGY5YjFkN2IxMSIsInZlciI6IjEuMCIsImlhdCI6MTU4ODYzOTMyMn0.hVAD5SZ4hIsyYdZgKPT0LBzHP3Ud5aU8vHXWQBcCMMOcjb5ZApiZSjzI7fdEQHuesudQtgwZumui-1a_XIV6v6jls5I_SlCr-h5bKJwa1VAW7_oKmKVxEjqt60dVJU8LIizySXimNXpS8W-YUHz0HBptE1vHndwadOT2OvB2ZOOHhNUnpNBdxaCYR-0TdSeH2ZnpXs6mphzxyRdD8-Bt7BB4FJZUNH63HpsJ3cV7aO08FrJ0jkveIdwcFy2WZbW-i1B8NWaWgPOpyx3DTWm3UCfJsLmVy21d6sK8LBL-vRaBfiSIfR9I1L2W_hB9U-TQMaTwQkAuXh4cNmg2u7GT8P"

I get an error message
WWW-Authenticate: Bearer error="invalid_token", error_description="The signature key was not found"
In another project, I am able to retrieve list of users using GraphServiceClient

Nugets in project:
<PackageReference Include="Microsoft.Graph" Version="3.4.0" />
<PackageReference Include="Microsoft.Graph.Auth" Version="1.0.0-preview.4" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.12.0" />
<PackageReference Include="Microsoft.Identity.Web" Version="0.1.1-preview" />
Hello @prabh-62 : did you look at this sample: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/2-WebApp-graph-user/2-1-Call-MSGraph
Yes, I looked through multiple projects in that git repository. I tried the same approach as 4-WebApp-your-API/4-1-MyOrg.


I still get the same error.
HTTP/1.1 401 Unauthorized
Date: Tue, 05 May 2020 19:08:39 GMT
Server: Kestrel
Content-Length: 0
WWW-Authenticate: Bearer error="invalid_token", error_description="The signature key was not found"

Approach 2-1-Call-MSGraph uses Microsoft.Identity.Web.UI and I am building an API so I cannot use that example.
IF you are building a Web API you need to look at https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2
Would your API require JWE? (encrypted tokens) ?
Access tokens in the API are normal JWTs.
I copied the exact approach from 3.-Web-api-call-Microsoft-graph-for-personal-accounts
services.AddProtectedWebApi(Configuration)
.AddProtectedWebApiCallsProtectedWebApi(Configuration)
.AddInMemoryTokenCaches();

Did you try this one?
https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2/tree/master/2.%20Web%20API%20now%20calls%20Microsoft%20Graph
(it can also support MSAs)
These are the steps I followed
> git clone https://github.com/Azure-Samples/active-directory-dotnet-native-aspnetcore-v2.git> cd active-directory-dotnet-native-aspnetcore-v2/2.\ Web\ API\ now\ calls\ Microsoft\ Graph/TodoListService/appsettings.json> dotnet run

I was able to get the AzureAdB2C Authentication working with the ASP.NET Core.
public void ConfigureServices(IServiceCollection services)
{
var discoveryPoint = "https://login.microsoftonline.com/{DirectoryID}/v2.0/.well-known/openid-configuration?p={Policy}";
var configManager =
new ConfigurationManager<OpenIdConnectConfiguration>(
discoveryPoint,
new OpenIdConnectConfigurationRetriever()
);
var config = configManager.GetConfigurationAsync(CancellationToken.None).GetAwaiter().GetResult();
services.AddProtectedWebApi(options =>
{
Configuration.Bind("AzureAdB2C", options);
options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters
{
RequireSignedTokens = true,
ValidIssuer = "https://login.microsoftonline.com/{DirectoryId}/v2.0/",
ValidAudience = "{ClientId}",
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = config.SigningKeys,
ValidateLifetime = true
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
context.Response.OnStarting(async () =>
{
context.NoResult();
context.Response.ContentType = "application/json";
byte[] bytes = Encoding.ASCII.GetBytes(context.Exception.Message);
await context.Response.Body.WriteAsync(bytes);
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
});
return Task.CompletedTask;
}
};
}, options => Configuration.Bind("AzureAdB2C", options));
}
However, there is one small hurdle. I had to comment certain code in file src/Microsoft.Identity.Web/WebApiAuthenticationBuilderExtensions.cs of Microsoft.Identity.Web library
options.Events.OnTokenValidated = async context =>
{
// This check is required to ensure that the Web API only accepts tokens from tenants where it has been consented and provisioned.
// if (!context.Principal.Claims.Any(x => x.Type == ClaimConstants.Scope)
// && !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Scp)
// && !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Roles)
// && !context.Principal.Claims.Any(y => y.Type == ClaimConstants.Role))
// {
// throw new UnauthorizedAccessException("Neither scope or roles claim was found in the bearer token.");
// }
await tokenValidatedHandler(context).ConfigureAwait(false);
};
Exact Line link: https://github.com/AzureAD/microsoft-identity-web/blob/master/src/Microsoft.Identity.Web/WebApiAuthenticationBuilderExtensions.cs#L132
Is it possible to not throw exceptions in certain situations?
Oh I see, @prabh-62 : you were trying to use B2C. the issue is that you have used
dotnet new webapi2 --auth Singleorg -n WeatherStation
and you should really use
dotnet new webapi2 --auth IndividualB2C-n WeatherStation
You shouldn't need to write all the code that you have written. Using just MIcrosoft.Identity.Web should work?
cc: @jennyf19
This morning, I tried again
> dotnet new webapi2 --auth IndividualB2C -n WeatherStationAzureAdB2C section in appsettings.jsonDisabled HTTPS redirection
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
IdentityModelEventSource.ShowPII = true;
}
// app.UseHttpsRedirection();
curl -i http://localhost:5000/weatherforecast -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ilg1ZVhrNHh5b2pORnVtMWtsMll0djhkbE5QNC1jNTdkTzZRR1RWQndhTmsifQ.eyJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNzNiOWUwZjgtNDY1Yy00MzY1LTkzNGUtMTM4ZmUxYjEyNDQ4L3YyLjAvIiwiZXhwIjoxNTg5MjEwMzcyLCJuYmYiOjE1ODkyMDMxNzIsImF1ZCI6IjliNzBjZTY4LTZlOWEtNGJiZC1iYWU3LWVhZGY5YjFkN2IxMSIsImlkcCI6IkxvY2FsQWNjb3VudCIsIm9pZCI6ImFjMWMwYzJmLWJiNjgtNDU1YS1hMTcxLTdiMjhjNDBlNTQwZCIsInN1YiI6ImFjMWMwYzJmLWJiNjgtNDU1YS1hMTcxLTdiMjhjNDBlNTQwZCIsImdpdmVuX25hbWUiOiJEZXZlbG9wbWVudCIsImZhbWlseV9uYW1lIjoiR3JvdXAiLCJuYW1lIjoiRGV2ZWxvcG1lbnQgR3JvdXAiLCJuZXdVc2VyIjpmYWxzZSwiZXh0ZW5zaW9uX0NvbXBhbnkiOiJNSUNST0RFQSIsImV4dGVuc2lvbl9QaG9uZSI6IjU1NTU1NTU1NTUiLCJlbWFpbHMiOlsiZGV2ZWxvcG1lbnRncm91cEBtaWNyb2RlYS5jb20iXSwidGZwIjoiQjJDXzFfTG9hZFBhbERldiIsImF6cCI6IjliNzBjZTY4LTZlOWEtNGJiZC1iYWU3LWVhZGY5YjFkN2IxMSIsInZlciI6IjEuMCIsImlhdCI6MTU4OTIwMzE3Mn0.JIQkmQ_3MTwpt3WheWERdY7YEDx48Yef3kitiUW-2tnThOACqblWGxBSek8HarAyvahlVr6jnENuipvW_px6nJXu0EWZMJTDQ9wkeIPdVNlbws3iwvh2hctTFUAztVWo_noSdFRqeb_U95sTLsCTEu3ck4mLBYOxuCsHhKlyeui_8n6R57J4-B0Jjk_BUsadsS_0GHKHJeAaJ14M2QEtertUZIgQQIqFb07OwrB_PIvyTZxaofPAwKEkvWwwMtns-xjCpQNlje41py0eUl6cfDjIUpOWyl2alk5ASjrqaZK2sYx3JDkSUDun33UOh9VIcog_JBQ7HVREF97bCVnp7w"And I get an exception

https://login.microsoftonline.com/{DirectoryID}/v2.0/.well-known/openid-configuration - I can confirm, this URL does return OpenID Configuration
I am not sure why SignUpSignInPolicyId: B2C_1_SignupSignin is included in the URL
@prabh-62 What is the instance and domain value in appsettings.json for the b2c tenant?
This is how my appsettings.json looks like
{
"AzureAdB2C": {
"Instance": "https://login.microsoftonline.com",
"ClientId": "Guid",
"B2cAppId": "Guid",
"DirectoryId": "Guid",
"Domain": "MicrodeaDev.onmicrosoft.com",
"SignUpSignInPolicyId": "B2C_1_SignupSignin"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
@prabh-62 thank you.
If you're using B2C, you have to include the policy in order to get the correct metadata and endpoints.
So, if you're using login.microsoftonline.com + policy (which is being deprecated as a b2c endpoint), you need to include the policy:
https://login.microsoftonline.com/tfp/{domain}/{policy}/v2.0/.well-known/openid-configuration
Did you create an app in your B2C tenant? You cannot mix the AzureAd settings and B2C settings, they are separate authorization servers at the moment, if i'm understanding your scenario correctly.
You should also be using *.b2clogin.com now as the tenant. Let me know if this helps.
Thank you for helping me troubleshoot. We will be migrating to *.b2clogin.com soon. I updated the appsettings.json
{
"AzureAdB2C": {
"Instance": "https://login.microsoftonline.com/tfp/",
"ClientId": "Guid",
"B2cAppId": "Guid",
"DirectoryId": "Guid",
"Domain": "MicrodeaDev.onmicrosoft.com",
"SignUpSignInPolicyId": "B2C_1_SignupSignin"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
Also, I had to comment out the following lines in the library

The authentication is working now with the following startup code.

馃
@prabh-62 thanks for the quick reply.
@jmprieur how would you like to proceed w/handling this use case (b2c w/login.microsoftonline.com)?
Thanks @jennyf19
Do we want to throw in Microsoft.Identity.Web if we detect B2C (a user flow) and authority is login.microsoftonline.com without the tfp ? We could have a meaningful exception advising to use b2cloging.com? what do you think?
@jmprieur let's discuss.
@prabh-62 i updated the title, as i believe this was the root cause. will make it easier for us to track. hope you don't mind. :)
One of the issues is for a b2C web app that calls a web API, if the developer provides the tfp in the authority in the appsettings.json, the sign-in part of the Oauth code flow works, but Microsoft.Identity.Web adds a second /tfp in the authority, and MSAL fails.
{
"AzureAdB2C": {
"Instance": "https://login.microsoftonline.com/tfp/",
Possible work around for this case (and only this case) would be to add the following line after
https://github.com/AzureAD/microsoft-identity-web/blob/4ab0da4c93ef554fab397cb4f1a873ff62d392c3/src/Microsoft.Identity.Web/TokenAcquisition.cs#L152
// Consider the case where the authority in the config ends with /tfp
authority = authority.Replace("/tfp/tfp", "/tfp");
This is an issue because the ASP.NET Core templates do that.
Proposal:
Included in 1.4 Release.
Most helpful comment
@prabh-62 i updated the title, as i believe this was the root cause. will make it easier for us to track. hope you don't mind. :)