4

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

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

I edited the post with the result of implementation of your answer
Note for those on IdentityServer3 - the answer above is 100% valid, other than AuthenticationType must be unique, in place of AuthenticationScheme

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.