Microsoft-identity-web: [Bug] Protecting WebAPI does not work if one wants to use multiple bearer schemes, e.g. for AAD *and* B2C

Created on 18 Aug 2020  路  6Comments  路  Source: AzureAD/microsoft-identity-web

Using Microsoft Identity Web 0.2.3-preview protecting WebAPI with multiple Bearer schemes for different authorities.

Referring to this sample for multi bearer auth for Web Apis, it is impossible to use Microsoft.Identity.Web package to accomplish the required setup.

Steps to reproduce:

Create the following configuration file (only relevant sections are listed here for clarity):

{
  "AzureAdB2C": {
    "Instance": "https://snetga.b2clogin.com/",
    "ClientId": "3422b1f4-8039-4023-9373-dbe747b40125",
    "TenantId": "126bbbd5-e975-43a7-88ac-babc84279788",
    "Domain": "snetga.onmicrosoft.com",
    "SignUpSignInPolicyId": "B2C_1A_signup_signin_google"
  },
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "126bbbd5-e975-43a7-88ac-babc84279788",
    "ClientId": "3422b1f4-8039-4023-9373-dbe747b40125"
  }
}

Then try to register both AzureAd and AzureAdB2C the following way:

            services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAdB2C", "B2C");
            services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAd", "v2.0");

            services.AddAuthorization(options =>
            {
                options.DefaultPolicy = new AuthorizationPolicyBuilder()
                    .RequireClaim("iss")
                    .AddAuthenticationSchemes("B2C", "v2.0")
                    .Build();
            }
            );

If the order is like here displayed - first registering AzureAdB2C and then registering AzureAd, you will get the following error when calling the API with valid token:

System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://login.microsoftonline.com/snetga.onmicrosoft.com/B2C_1A_signup_signin_google/v2.0/.well-known/openid-configuration'.
 ---> System.IO.IOException: IDX20807: Unable to retrieve document from: 'https://login.microsoftonline.com/snetga.onmicrosoft.com/B2C_1A_signup_signin_google/v2.0/.well-known/openid-configuration'. HttpResponseMessage: 'StatusCode: 404, ReasonPhrase: 'Not Found', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{

If changing the order - first register the AzureAd and then AzureAdB2C, e.g.:

            services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAd", "v2.0");
            services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAdB2C", "B2C");
            ...

You will get success (200) calling the WebAPI with Azure AD B2C token, but 401 calling the API using v2.0 Token.

If you only use one of them (either AzureAd only or AzureAdB2C only) - it works.

Also, if you use the Microsoft.Identity.Web extensions to register only one of the providers (AzureAd OR AzureAdB2C), but use the Microsoft.AspNetCore.Authentication.XXX for the other provider, e.g.:

          services.AddAuthentication()
            .AddJwtBearer("v2.0", options =>
            {
                options.Authority = $"{Configuration["AzureAd:Instance"]}/{Configuration["AzureAd:TenantId"]}/v2.0";
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = true,
                    ValidIssuer = $"{Configuration["AzureAd:Instance"]}/{Configuration["AzureAd:TenantId"]}/v2.0",
                    ValidateAudience = true,
                    ValidAudience = Configuration["AzureAd:ClientId"],
                    ValidateLifetime = true
                };
            }); services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAd", "v2.0");

            services.AddMicrosoftWebApiAuthentication(Configuration, "AzureAdB2C", "B2C");


            services.AddAuthorization(options =>
            {
                options.DefaultPolicy = new AuthorizationPolicyBuilder()
                    .RequireClaim("iss")
                    .AddAuthenticationSchemes("B2C", "v2.0")
                    .Build();
            }
            );

Then the API accepts both AzureAd (v2.0) AND AzureAdB2C tokens.

After some very short search through the code, it seems this registration in WebApiAuthenticationBuilderExtensions might be causing the issue. Because of builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions<MicrosoftIdentityOptions>, MicrosoftIdentityOptionsValidation>()), which is trying to add a singleton of MicrosoftIdentityOptions

  • basically the second use of AddMicrosoftWebApiAuthentication will override any configuration settings that are different from the first registration. If I read correctly the documentation about TryAddEnumerable, it explains the behavior I am observing. But this is just a guess.

Expected behavior is that I am able to use AddMicrosoftWebApiAuthentication multiple times with different configuration settings. There are a lot of valid scenarios where one would like to have this:

  • Using Azure AD B2C for end-users and client credentials flow (which is exactly what this sample is about)
  • Having an application that authenticates users from different authorities - e.g. Commercial and China/GCC etc.
P2 bug duplicate fixed

Most helpful comment

Thanks @astaykov for investigation and a write-up.

This is also a duplicate of #429. I wrote a reply there.

The issue is with calling builder.Services.Configure(configureMicrosoftIdentityOptions); twice - with second time overwriting the first options.

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions<MicrosoftIdentityOptions>, MicrosoftIdentityOptionsValidation>()); I think is fine because it adds a singleton of a validator class, not options class.

All 6 comments

@astaykov
By using AddMicrosoftWebApiAuthentication, this also sets the default scheme.

Do you try?

services.AddAuthentication()
 .AddMicrosoftWebApi(Configuration, "AzureAdB2C", "B2C")
 .AddMicrosoftWebApi(Configuration, "AzureAd", "v2.0");

You make a good point about the options. We might need to try to use named options (with an IOptionsSnapshot) for MicrosoftIdentityOptions to support this scenario @jennyf19 and @pmaytak

@astaykov; Does your work around fully work for you? is it ok to prioritize this fix after our 0.3.0-preview?

Thanks @astaykov for investigation and a write-up.

This is also a duplicate of #429. I wrote a reply there.

The issue is with calling builder.Services.Configure(configureMicrosoftIdentityOptions); twice - with second time overwriting the first options.

builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions<MicrosoftIdentityOptions>, MicrosoftIdentityOptionsValidation>()); I think is fine because it adds a singleton of a validator class, not options class.

@astaykov
By using AddMicrosoftWebApiAuthentication, this also sets the default scheme.

Do you try?

services.AddAuthentication()
 .AddMicrosoftWebApi(Configuration, "AzureAdB2C", "B2C")
 .AddMicrosoftWebApi(Configuration, "AzureAd", "v2.0");

You make a good point about the options. We might need to try to use named options (with an IOptionsSnapshot) for MicrosoftIdentityOptions to support this scenario @jennyf19 and @pmaytak

@astaykov; Does your work around fully work for you? is it ok to prioritize this fix after our 0.3.0-preview?

Using the suggested approach has absolutely same (erronous) results like in my original approach.
My workaround works fully, and it is so far fine to work this way given the preview status of the package.

Thanks @astaykov for all the feedback. A fix is in PR #475 (which will be included in the next release). Also added a small section to the wiki related to this. I'll close this issue as duplicate of #429.

Included in 0.4.0-preview release

Included in 0.4.0-preview release

Was this page helpful?
0 / 5 - 0 ratings