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

[question] Is it possible to add AzureAD *and* AzureAD B2C authentication to the same web app? #11972

Closed
alexvy86 opened this issue Jul 8, 2019 · 17 comments
Labels
area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer
Milestone

Comments

@alexvy86
Copy link

alexvy86 commented Jul 8, 2019

I've been struggling with this scenario for a bit and I'm starting to believe it might not be supported (at least in 2.2?), so I wanted to see if anyone can confirm that or point me on the right direction.

What I want to achieve is to allow different tenants in my multi-tenant application, to use different authentication providers (AzureAD for one, AzureAD B2C for another).

I'm registering separate schemes (Authentication, OpenID, Cookie, etc) for each tenant, like this (and similar for AzureAD):

var azureAdB2CConfigs = Configuration.GetSection("AzureAD_B2C").GetChildren();
foreach (var tenantSpecificConfig in azureAdB2CConfigs)
{
	var tenantName = tenantSpecificConfig.Key;
	services.AddAuthentication().AddAzureADB2C(
		Utils.GetTenantScopedScheme(AzureADB2CDefaults.AuthenticationScheme, tenantName),
		Utils.GetTenantScopedScheme(AzureADB2CDefaults.OpenIdScheme, tenantName),
		Utils.GetTenantScopedScheme(AzureADB2CDefaults.CookieScheme, tenantName),
		Utils.GetTenantScopedScheme(AzureADB2CDefaults.DisplayName, tenantName),
		options => tenantSpecificConfig.Bind(options));
}

If I only call AddAzureAD() or AddAzureADB2C() on the authentication builder, things work as expected. But if I call both, I start getting the error below when I make any request to my application (e.g. even just for the favicon). Of note, it doesn't happen immediately at startup, but only when I make the first request:

image

The order in which I register the AzureAD and AzureADB2C providers doesn't seem to matter.

I looked at the code for AddAzureAD() and AddAzureADB2C (for release/2.2) and I started suspecting that the issue comes from the fact that they both try to register singleton configurators for OpenIdConnectOptions (here and here), so maybe one of them isn't getting that object configured as it expects it to be. In master, those changed from TryAddSingleton to TryAddEnumerable (here and here). While I don't yet understand how each provider would get the correct OpenIdConnectOptions in this scenario, based on #4635 I imagine it might help...? @javiercn I hope it's not too intruding to tag you directly, but since you found the problem for #4635 and I think this might be similar I thought it might be useful to include you.

For completeness, this is the relevant section of my appsettings.json (which I believe is fine, because as I said, either AzureAD or AzureADB2C on its own works correctly).

"AzureAD": {
	"<tenant1>": {
		"Instance": "https://login.microsoftonline.com/",
		"Domain": "<tenant1-domain>.onmicrosoft.com",
		"TenantId": "organizations",
		"ClientId": "<tenant1-clientid>",
		"CallbackPath": "/signin-oidc"
	} 
},
"AzureAD_B2C": {
	"<tenant2>": {
		"Instance": "https://<tenant2-domain>.b2clogin.com/",
		"ClientId": "<tenant2-clientid>",
		"ClientSecret": "<secret>",
		"CallbackPath": "/b2c-signin-oidc",
		"Domain": "<tenant2-domain>.onmicrosoft.com",
		"SignUpSignInPolicyId": "B2C_1_SignUpOrSignIn"
	} 
}
@blowdart
Copy link
Contributor

blowdart commented Jul 8, 2019

@jmprieur is this doable?

@jmprieur
Copy link
Contributor

jmprieur commented Jul 8, 2019

@blowdart : I don't think it is. I need to try out

@blowdart blowdart added the area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer label Jul 8, 2019
@javiercn
Copy link
Member

javiercn commented Jul 8, 2019

I think this is a known issue in 2.2. We fixed it in 3.0

@javiercn
Copy link
Member

javiercn commented Jul 8, 2019

The workaround is to do the options configuration yourself

@alexvy86
Copy link
Author

alexvy86 commented Jul 8, 2019

@javiercn I thought about that but couldn't figure out a couple of things. First off I imagine I'd register a single implementation of IConfigureNamedOptions<OpenIdConnectOptions> before I call either of the auth providers, to ensure that my singleton is the one registered. Now, in order to know "who" am I building the options for (AzureAD or AzureADB2C), I'd need to check the scheme that was requested, correct? Which would be the name parameter of the Configure method in my options configurator class.

And the issue I hit was, the classes that do this for the framework (OpenIdConnectOptionsConfiguration in the AzureAD and AzureADB2C projects) depend on IOptions<AzureADSchemeOptions> (and a corresponding for B2C) but those are internal so I wouldn't be able to use them myself... do you have any pointers on what do those accomplish, and how would I be able to do it on my own?

@analogrelay analogrelay added this to the Discussions milestone Jul 11, 2019
@plaisted
Copy link

I had this same issue and tried figuring out some way to make them play nice together but was not able to.

Doing the configuration manually did not seem practical as it would require essentially rewriting the module due to internal only configuration classes (eg. AzureADB2COpenIDConnectEventHandlers).

@plaisted
Copy link

Okay spoke to soon, seem to have it working as ugly as it is...

In Startup.ConfigureServices:

// hack to just grab preconfigured options
var dummyCollection = new ServiceCollection();
dummyCollection.AddAuthentication("dummy").AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options));
var sp = dummyCollection.BuildServiceProvider();
var openIdSnapshot = sp.GetService<IOptionsSnapshot<OpenIdConnectOptions>>();
var b2cOpenIdOpts = openIdSnapshot.Get(AzureADB2CDefaults.OpenIdScheme);
var cookieSnapshot = sp.GetService<IOptionsSnapshot<CookieAuthenticationOptions>>();
var b2cCookieOpts = cookieSnapshot.Get(AzureADB2CDefaults.CookieScheme);

services.AddAuthentication("AADorB2C")
        .AddPolicyScheme("AADorB2C", "Selects AAD or B2C", o =>
        {
            o.ForwardDefaultSelector = ctx =>
            {
                 // app logic to select if you want to use AAD or B2C
            };
        })
        .AddAzureAD(options => Configuration.Bind("AzureAd", options))
        .AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options));

// configure the B2C options here by copying the pertinent values from above
services.Configure<OpenIdConnectOptions>(AzureADB2CDefaults.OpenIdScheme, c => {
    c.ClientId = b2cOpenIdOpts.ClientId;
    c.ClientSecret = b2cOpenIdOpts.ClientSecret;
    c.Authority = b2cOpenIdOpts.Authority;
    c.CallbackPath = b2cOpenIdOpts.CallbackPath;
    c.SignedOutCallbackPath = b2cOpenIdOpts.SignedOutCallbackPath;
    c.SignInScheme = b2cOpenIdOpts.SignInScheme;
    c.TokenValidationParameters = b2cOpenIdOpts.TokenValidationParameters;
    c.Events = b2cOpenIdOpts.Events;
    // add other non-default options you may want
});

// do same for cookies (although less important, just sets the redirect URLs)

@jpda
Copy link

jpda commented Nov 21, 2019

I have this issue too, in 3.0 & 3.1.

Between AddAzureAd and AddAzureAdB2C, six schemes get added:

AzureAD
AzureADOpenID
AzureADCookie
AzureADB2C
AzureADB2COpenID
AzureADB2CCookie

The options configuration for both OpenID schemes gets run on both the AAD and B2C OIDC options configurations. This is fine for the respective scheme, but fails for the other (see table below) because the scheme doesn't exist in the mappings (which are added to the OpenIDMappings dictionary on the AzureAD(B2C)SchemeOptions by the auth builder extension), so the options for that specific scheme can't be retrieved. I'm assuming this is happening because there are two implementations of IConfigureOptions<OpenIdConnectOptions> added, one for both AddAzureAD and AddAzureADB2C? This is an assumption, so if someone can enlighten me it would be appreciated.

Scheme OptionsConfiguration result
AzureADOpenID AzureADOpenIDConnectOptionsConfiguration ✔️ OK
AzureADB2COpenID AzureADOpenIDConnectOptionsConfiguration ❌ fails
AzureADOpenID AzureADB2COpenIdConnectOptionsConfiguration ❌ fails
AzureADB2COpenID AzureADB2COpenIdConnectOptionsConfiguration ✔️ OK

A null check on azureAdScheme and azureADB2CScheme gets past the problem, but that feels like it ignores the problem of differentiating between two implementations of IConfigureOptions<OpenIdConnectOptions>.

https://github.com/aspnet/AspNetCore/blob/e2def80a0ad5143fdbd72f2051a86b2082ca92ce/src/Azure/AzureAD/Authentication.AzureAD.UI/src/AzureADOpenIdConnectOptionsConfiguration.cs#L23-L24

https://github.com/aspnet/AspNetCore/blob/e2def80a0ad5143fdbd72f2051a86b2082ca92ce/src/Azure/AzureAD/Authentication.AzureADB2C.UI/src/AzureADB2COpenIdConnectOptionsConfiguration.cs#L25-L26

@jpda
Copy link

jpda commented Apr 16, 2020

For what it's worth, this bubbled back up, only this time it was with JwtBearerOptions. In config it is doing a TryAddSingleton of IConfigureOptions<JwtBearerOptions>, which exits if one exists - whereas an AddSingleton will append. There are enough other checks for scheme names that it seems to work in 2.x. Looks like it has been resolved with TryAddEnumerable in dotnet 5.

@alexvy86
Copy link
Author

So, I came back to this topic now that we finally moved our service to .NET Core 3.1 and while the error is different, the scenario still doesn't work. Is that the accepted consensus for 3.1? I was hopeful after @javiercn mentioned that it was fixed in 3.0.

@alexvy86
Copy link
Author

A bit extra context that I found: the conflict seems to be not just between AzureAD and AzureADB2C, if I add AzureAD and plain OIDC (authBuilder.AddOpenIdConnect()) I get the same issue. And to expand on my last comment, the different error seems to be because adding AzureAD sets up an instance of IValidateOptions<AzureADOptions>, which somehow is executing with the settings from the other providers (AzureADB2C or OIDC)?

From the ASPNETCore source code (release/3.1):
image

In my app, with symbols for some Microsoft packages loaded:
image

@alexvy86
Copy link
Author

One final comment from me: I tried the Microsoft.Identity.Web package and it seems to do the trick, no changes to my configuration, minor changes to my services configuration (.AddSignIn() instead of .AddAzureAD()/.AddAzureADB2C()), and I was able to get AzureAD, AzureADB2C, and generic OIDC working as providers on the same app.

@jmprieur
Copy link
Contributor

jmprieur commented May 16, 2020

@alexvy86 : we are very glad to read this (that was one of the goals of Microsoft.Identity.Web).
cc: @jennyf19 @pmaytak @henrik-me

Would you be willing to share (generic) configuration/code snippets to help others to achieve the same? (we intend to document it in MIcrosoft.Identity.Web documentation, but given you have done it, this would be a good head start :)).

@alexvy86
Copy link
Author

Sure, give me a couple of days and I should be able to clean up my app to work as a generic example.

@alexvy86
Copy link
Author

@jmprieur I created a repo with a sample app that uses 3 different auth providers, based on the official Microsoft samples. It does require some setup with the app registrations in AzureAD and AzureAD B2C, but hopefully it can serve as a starting point for someone.

@jmprieur
Copy link
Contributor

Thank you @alexvy86 !
Awesome!

@ghost
Copy link

ghost commented Nov 12, 2020

Thank you for contacting us. Due to a lack of activity on this discussion issue we're closing it in an effort to keep our backlog clean. If you believe there is a concern related to the ASP.NET Core framework, which hasn't been addressed yet, please file a new issue.

This issue will be locked after 30 more days of inactivity. If you still wish to discuss this subject after then, please create a new issue!

@ghost ghost closed this as completed Nov 12, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 12, 2020
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-auth Includes: Authn, Authz, OAuth, OIDC, Bearer
Projects
None yet
Development

No branches or pull requests

7 participants