Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] 'Scheme already exists: Bearer' when trying to setup both AAD and AAD B2C auth #429

Closed
pheuter opened this issue Aug 10, 2020 · 23 comments · Fixed by #475
Closed

[Bug] 'Scheme already exists: Bearer' when trying to setup both AAD and AAD B2C auth #429

pheuter opened this issue Aug 10, 2020 · 23 comments · Fixed by #475
Labels
bug Something isn't working duplicate This issue or pull request already exists enhancement New feature or request fixed investigate multiple auth schemes supported in v.1.10
Milestone

Comments

@pheuter
Copy link

pheuter commented Aug 10, 2020

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'

@jmprieur
Copy link
Collaborator

jmprieur commented Aug 10, 2020

@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 jmprieur added answered question Further information is requested labels Aug 10, 2020
@pheuter
Copy link
Author

pheuter commented Aug 10, 2020

@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'.

@pheuter
Copy link
Author

pheuter commented Aug 10, 2020

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.

@pmaytak
Copy link
Contributor

pmaytak commented Aug 11, 2020

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);
    })

@pheuter
Copy link
Author

pheuter commented Aug 11, 2020

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"
  }
}

@tymtam2
Copy link

tymtam2 commented Aug 13, 2020

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?

@pheuter
Copy link
Author

pheuter commented Aug 13, 2020

@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.

@jmprieur
Copy link
Collaborator

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

@pheuter
Copy link
Author

pheuter commented Aug 13, 2020

@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'.

@jennyf19 jennyf19 added this to To do in 0.4.0-preview via automation Aug 14, 2020
@jennyf19 jennyf19 added this to the 0.3.1-preview milestone Aug 14, 2020
@timClyburn
Copy link
Contributor

timClyburn commented Aug 16, 2020

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.

@pmaytak
Copy link
Contributor

pmaytak commented Aug 18, 2020

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?

public static AuthenticationBuilder AddMicrosoftWebApi(
this AuthenticationBuilder builder,
Action<JwtBearerOptions> configureJwtBearerOptions,
Action<MicrosoftIdentityOptions> configureMicrosoftIdentityOptions,
string jwtBearerScheme = JwtBearerDefaults.AuthenticationScheme,
bool subscribeToJwtBearerMiddlewareDiagnosticsEvents = false)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (configureJwtBearerOptions == null)
{
throw new ArgumentNullException(nameof(configureJwtBearerOptions));
}
if (configureMicrosoftIdentityOptions == null)
{
throw new ArgumentNullException(nameof(configureMicrosoftIdentityOptions));
}
builder.AddJwtBearer(jwtBearerScheme, configureJwtBearerOptions);
builder.Services.Configure(configureMicrosoftIdentityOptions);
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions<MicrosoftIdentityOptions>, MicrosoftIdentityOptionsValidation>());
builder.Services.AddHttpContextAccessor();
builder.Services.AddHttpClient();
if (subscribeToJwtBearerMiddlewareDiagnosticsEvents)
{
builder.Services.AddSingleton<IJwtBearerMiddlewareDiagnostics, JwtBearerMiddlewareDiagnostics>();
}
// Change the authentication configuration to accommodate the Microsoft identity platform endpoint (v2.0).
builder.Services.AddOptions<JwtBearerOptions>(jwtBearerScheme)
.Configure<IServiceProvider, IOptions<MicrosoftIdentityOptions>>((options, serviceProvider, microsoftIdentityOptions) =>
{

@Tratcher
Copy link

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

@timClyburn
Copy link
Contributor

@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.

@pmaytak
Copy link
Contributor

pmaytak commented Aug 20, 2020

@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

@jmprieur jmprieur added enhancement New feature or request and removed answered question Further information is requested labels Aug 26, 2020
@jmprieur jmprieur moved this from To do to In progress in 0.4.0-preview Aug 26, 2020
@pmaytak pmaytak self-assigned this Aug 26, 2020
@jmprieur
Copy link
Collaborator

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

@pmaytak
Copy link
Contributor

pmaytak commented Aug 27, 2020

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

@jennyf19
Copy link
Collaborator

fixed by customer @timClyburn

@pmaytak
Copy link
Contributor

pmaytak commented Aug 28, 2020

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.

@jmprieur
Copy link
Collaborator

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

@jennyf19
Copy link
Collaborator

Included in 0.4.0-preview release

@lnaie
Copy link

lnaie commented Feb 11, 2021

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.

@jmprieur
Copy link
Collaborator

@lnaie : yes; this is a duplicate of #955, which we'll address (there is a PR. you might want to try the branch)

@jmprieur jmprieur added the duplicate This issue or pull request already exists label Feb 11, 2021
@jennyf19 jennyf19 added this to To do in 1.10 via automation May 5, 2021
@jennyf19 jennyf19 modified the milestones: 0.4.0-preview, 1.10.0 May 5, 2021
@jennyf19 jennyf19 moved this from To do to In progress in 1.10 May 5, 2021
@jennyf19 jennyf19 added the multiple auth schemes supported in v.1.10 label May 5, 2021
@jennyf19 jennyf19 added this to To do in 1.11 via automation May 14, 2021
@jennyf19 jennyf19 removed this from In progress in 1.10 May 14, 2021
@jennyf19 jennyf19 modified the milestones: 1.10.0, 1.11.0 May 14, 2021
@jennyf19 jennyf19 moved this from To do to Done in 1.11 May 19, 2021
@jennyf19
Copy link
Collaborator

Included in 1.11.0 release and documentation here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working duplicate This issue or pull request already exists enhancement New feature or request fixed investigate multiple auth schemes supported in v.1.10
Projects
No open projects
0.4.0-preview
  
Done
1.11
Done
Development

Successfully merging a pull request may close this issue.

8 participants