Microsoft-identity-web: [Bug] 'Scheme already exists: Bearer' when trying to setup both AAD and AAD B2C auth

Created on 10 Aug 2020  路  22Comments  路  Source: AzureAD/microsoft-identity-web

Which version of Microsoft Identity Web are you using?
0.2.3-preview

Where is the issue?

I'm trying to make my ASP.NET Core Web API compatible with both AAD tokens issued on behalf of applications as well as AAD B2C tokens issued on behalf of users, but run into errors when trying to configure both entries in my appsettings.json file. If I only initialize AddMicrosoftWebApi once, then I get issues verifying JWT signature when the token is generated using the identity provider that was left out.

Is this a new or an existing app?
c. This is a new app

Repro

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
             .AddMicrosoftWebApi(this.Configuration, "AzureAd")
             .AddMicrosoftWebApi(this.Configuration, "AzureAdB2C")

Expected behavior
ASP.NET Core app is setup to validate tokens issued from both identity providers.

Actual behavior
Error during startup: InvalidOperationException: 'Scheme already exists: Bearer'

bug duplicate enhancement fixed investigate multiple auth schemes

All 22 comments

@AzureAD/azure-ad-app-content-authors

You added twice the same authentication scheme. You'd need to use a different name for the jwtBearerScheme parameter in one of your calls. for instance:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
             .AddMicrosoftWebApi(this.Configuration, "AzureAd")
             .AddMicrosoftWebApi(this.Configuration, "AzureAdB2C", "jwtBearerScheme2")

BTW, the default one will be "Bearer" (JwtBearerDefaults.AuthenticationScheme).

@jmprieur Got it, that makes sense! I am seeing a new error now:

info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[1]
Failed to validate the token. 
Microsoft.IdentityModel.Tokens.SecurityTokenSignatureKeyNotFoundException: IDX10501: Signature validation failed. Unable to match key: kid: 'huN95IvPfehq34GzBDZ1GXGirnM'.

One thought that just occurred to me is if it's even possible to support passing different Authorization: Bearer tokens to the same Web Api that are issued by different providers, i.e. one from Active Directory and another from AAD B2C?

EDIT: This documentation section suggests it should be possible, but unclear how to set it up using Microsoft Identity Web.

The IDX10501 was thrown when the issuer signing key was found but didn't match the token. I think all the keys were retrieved from IDP but the wrong one is used to validate the token.

@GeoK @mafurman @brentschmaltz

If necessary, you can use an overload of AddMicrosoftWebApi to configure options like TokenValidationParameters:

.AddMicrosoftWebApi(
    jwtBearerOptions =>
    {
        Configuration.Bind("AzureAd", jwtBearerOptions);
        jwtBearerOptions.TokenValidationParameters.IssuerSigningKeyValidator = ;// custom code
    },
    microsoftIdentityOptions =>
    {
        Configuration.Bind("AzureAd", microsoftIdentityOptions);
    })

Strangely enough I get a different error when I switch the order of AddMicrosoftWebApi() calls, so if I first setup AzureAdB2C instead of AzureAd, I'll get a different error:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddMicrosoftWebApi(this.Configuration, "AzureAdB2C")
                .AddMicrosoftWebApi(this.Configuration, "AzureAd", "jwtBearerScheme2")
System.InvalidOperationException: IDX20803: Unable to obtain configuration from:
'https://login.microsoftonline.com/mytenant.onmicrosoft.com/B2C_1_SignIn/v2.0/.well-known/openid-configuration'.

It appears that the Instance configuration is overridden with the second call to AddMicrosoftWebApi(), so instead of using "Instance": "https://mytenant.b2clogin.com" it uses "Instance": "https://login.microsoftonline.com/".

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "mytenant.onmicrosoft.com",
    "ClientId": "<UUID>",
    "TenantId": "<UUID>"
  },
  "AzureAdB2C": {
    "Instance": "https://mytenant.b2clogin.com",
    "Domain": "mytenant.onmicrosoft.com",
    "ClientId": "<UUID>",
    "SignUpSignInPolicyId": "B2C_1_SignIn"
  }
}

The doco @pheuter refers to (Use multiple authentication schemes) uses 2 stage process:

Step 1:

public void ConfigureServices(IServiceCollection services)
{
    // Code omitted for brevity

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://localhost:5000/identity/";
        })
        .AddJwtBearer("AzureAD", options =>
        {
            options.Audience = "https://localhost:5000/";
            options.Authority = "https://login.microsoftonline.com/eb971100-6f99-4bdc-8611-1bc8edd7f436/";
        });
}

Step 2:

The next step is to update the default authorization policy to accept both authentication schemes. For example:

services.AddAuthorization(options =>
    {
        var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(
            JwtBearerDefaults.AuthenticationScheme,
            "AzureAD");
        defaultAuthorizationPolicyBuilder = 
            defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
        options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
    });

When using AddMicrosoftWebApiAuthentication or AddMicrosoftWebApi , would the 2nd step be required?

@tymtam2 The second stage is separate from the first in the same way Authorization is separate from Authentication in ASP.NET. The problems I'm describing all occur in the first stage when attempting to authenticate Bearer tokens created by two different issuers: AAD and AAD B2C.

You can see in your first code snippet that the docs show how to specify two different authorities. The issue I'm seeing with this library is that .AddMicrosoftWebApi(this.Configuration, "AzureAdB2C") should handle that part, but there's likely a bug in how it pulls and stores the configuration.

@pheuter: do you have repro steps so that we can debug? a repro project?

@jmprieur I just created this repo: https://github.com/pheuter/IdentityWebRepro

You'll need to fill in your own AAD and AAD B2C configuration in appsettings.json. Once you do, you'll notice that when you try to hit the authorized WeatherController endpoint, you'll get an error that suggests it's not properly pulling in the configuration.

EDIT: Specifically, this error I shared above:

System.InvalidOperationException: IDX20803: Unable to obtain configuration from:
'https://login.microsoftonline.com/mytenant.onmicrosoft.com/B2C_1_SignIn/v2.0/.well-known/openid-configuration'.

I wonder if it's related to this section of code within the AddMicrosoftWebApi extension method:

image

Which seems to apply a configuration to the services. Executing AddMicrosoftWebApi(Configuration, "") twice seems to merge the two sets together, the final config seems to depend on the order that you add them.

Looking at the services collection shows only one MicrosoftIdentityOptions configuration.

Thanks everyone for your investigations. @timClyburn, you're right.

When we call AddMicrosoftWebApi twice (for each configuration), Line 81 adds two options delegates to the service collection. Both configure actions are run, and second one overwrites the first. Then Line 94 resolves IOptions<MicrosoftIdentityOptions> as merged options.

@tratcher, I tried using the IOptionsSnapshot on Line 94, but get an exception of unable to resolve scoped service from root provider. Is there a better pattern to retrieve named options there?

https://github.com/AzureAD/microsoft-identity-web/blob/876cb8645e9f1c064b4db32db747eae593c7c305/src/Microsoft.Identity.Web/WebApiExtensions/WebApiAuthenticationBuilderExtensions.cs#L58-L95

It seems like you're set up for a single MicrosoftIdentityOptions across all providers? To get around that you need to use named options. The auth scheme works as a unique name in this case.
https://github.com/dotnet/aspnetcore/blob/0e592df3ecfb402fc33cd8e31603b217393b5f94/src/Security/Authentication/Core/src/AuthenticationBuilder.cs#L42

But then at the consumption point you also need to resolve it by name.
https://github.com/dotnet/aspnetcore/blob/6e54e06cfa5d9c3e43f68ab396664d70d22c6d20/src/Security/Authentication/Core/src/AuthenticationHandler.cs#L85

@Tratcher @pmaytak can someone point me in the right direction for completing the contributor licence agreement? I have followed the link and can have the sample pdf. I can't see where or how to submit anything?

I have a code change completed and passes the tests for changing this to use IOptionsMonitor and named options but I need to sign the agreement before I can contribute.

@timClyburn I'm not exactly sure but I think if you just try to submit a pull request, the CLA bot will post a link where to sign the CLA.
image

@pmaytak : is there more work to do on this one?

@jmprieur Just testing a solution and will write a wiki article.

fixed by customer @timClyburn

Thanks everyone for 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.

Thanks everybody
@pmaytak it might be worth describing the appconfig.json, and possibly the attribute
Thx

Included in 0.4.0-preview release

I'm using the version 1.5.1 and there is still a problem with a custom Bearer scheme for a single AzureAD authentication middleware . Here is a repro project: https://github.com/lnaie/azuread-poc.

For now I'm using a single AzureAd auth middleware in the API project, but the goal is to get to use 2 of them, as many pointed out here, one with the default Bearer scheme (that works) and one with a custom scheme (that I can't get it to work even with a single middleware).
I need help with this.

@lnaie : yes; this is a duplicate of https://github.com/AzureAD/microsoft-identity-web/issues/955, which we'll address (there is a PR. you might want to try the branch)

Was this page helpful?
0 / 5 - 0 ratings