Search code examples
asp.net-coreazure-active-directoryidentityserver4

Enable multiple AzureAd in Identityserver4 .NET Core


I'm trying to enable login-functionality to multiple AzureAD's in one identity server.

The reason this is necessary, is that multiple tenants need to be able to login to their own AzureAD through 1 identityserver. Setting these up will happen dynamically.

For this example, I have made an extension to AuthenticationBuilder which allows passing multiple AzureAd's and adds them to the AuthenticationBuilder.

//startup.cs
        Dictionary<string, string> azure1 = new Dictionary<string, string>();
        azure1.Add("name", "azure1");
        azure1.Add("clientid", <azure1id>);
        azure1.Add("tenantid", <azure1tenant>);
        Dictionary<string, string> azure2 = new Dictionary<string, string>();
        azure2.Add("name", "azure2");
        azure2.Add("clientid", <azure2id>);
        azure2.Add("tenantid", <azure2tenant>);
        Dictionary<string, string>[] ids = { azure1, azure2 };

        services.AddAuthentication()
        .AddCookie()
        .AddAzureAdAuthentications(ids)


//AuthenticationBuilderExtension
    public static AuthenticationBuilder AddAzureAdAuthentications(this AuthenticationBuilder builder, Dictionary<string, string>[] azureAds)
    {
        string name = "";
        string clientid = "";
        string tenantid = "";
        foreach (Dictionary<string, string> azuread in azureAds)
        {
            name = azuread.GetValueOrDefault("name");
            clientid = azuread.GetValueOrDefault("clientid");
            tenantid = azuread.GetValueOrDefault("tenantid");
            builder.AddOpenIdConnect(name, name, options =>
            {
                options.Authority = $"https://login.microsoftonline.com/{tenantid}";
                options.ClientId = clientid;
                options.ResponseType = OpenIdConnectResponseType.IdToken;
            });
        }
        return builder;
    }

I would expect the above code to add 2 azureAd's as a login options, this works fine. I would think the authentication-flow would stay intact.

Instead, 2 problems appear.

  1. Both loginoptions lead to the last configuration (azure2), azure1 just vanishes. Which means the last configuration is overwriting the first.
  2. When logging in to azure2 with a valid account, logging in to the identityserver fails and the following error occurs:

Exception: Correlation failed. Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler+d__12.MoveNext()

Removing 1 of the 2 AzureAD's from the dictionary results in everything working fine.

UPDATE

Implementing leastprivileges answer (thanks) results in the following: It "almost" works. The AzureAD's are recognized correctly, leading to the correct flows and accepting only the corresponding accounts. But now the client is not succesfully logged in after the callback to the identityserver.

I feel it's just a small edit regarding the signinscheme, but i can't really figure it out as the documention on this is quite minimal.

foreach (Dictionary<string, string> azuread in ids)
        {
            string name = azuread.GetValueOrDefault("name");
            string clientid = azuread.GetValueOrDefault("clientid");
            string tenantid = azuread.GetValueOrDefault("tenantid");
            services.AddAuthentication(name).AddCookie($"Cookie{name}").AddOpenIdConnect(name, name, options =>
            {
                options.SignInScheme = $"Cookie{name}";
                options.SignOutScheme = $"Cookie1{name}";
                options.CallbackPath = $"/signin-{name}";
                options.Authority = $"https://login.microsoftonline.com/{tenantid}";
                options.ClientId = clientid;
                options.ResponseType = OpenIdConnectResponseType.IdToken;
            });
        }

Solution

Might not be that difficult, but sharing the solution to help others:

            services.AddAuthentication().AddCookie($"Cookie{name}")
            .AddOpenIdConnect(name, name, options =>
            {
                options.CallbackPath = $"/signin-{name}";
                options.Authority = $"https://login.microsoftonline.com/{tenantid}";
                options.ClientId = clientid;
                options.ResponseType = OpenIdConnectResponseType.IdToken;
            });

Solution

  • Each handler needs at least a unique CallbackPath - there are other callbacks for signout and post redirect - they must be set as well if you are using those features.

    Also the authentication scheme must be unique.