Multiple AD authentication schemas – cannot run .NET 7 web API when I add multiple providers.

huangapple go评论64阅读模式
英文:

Multiple AD authentication schemas - cannot run .NET 7 web API when I add multiple providers

问题

I'm building a multi-tenant SaaS application and I need to have multiple ADs as the authenticators to the application.

当我一次配置一个AD时,它可以正常工作100%。
问题是当我需要初始化多个Microsoft Identity提供程序时。

在这个解决方案中,我建立了一个Singleton,它将加载所有提供程序的信息。目前,它是这样模拟的:

public class TenantProvider
{
    private static TenantProvider _instance;
    private static readonly object _lock = new object();

    public List<Tenant> tenants { get; set; }

    private TenantProvider() 
    { 
        tenants = new List<Tenant>();

        //模拟1
        tenants.Add(new Tenant
        {
            instance = "https://login.microsoftonline.com/",
            domain = "MyDomain",
            tenantId = "MyTenantId",
            clientId = "MyClientId",
            callbackPath = "/signin-oidc",
            allowedHosts = "*"
        });

        //模拟2
        tenants.Add(new Tenant
        {
            instance = "https://login.microsoftonline.com/",
            domain = "MyDomain2",
            tenantId = "MyTenantId2",
            clientId = "MyClientId2",
            callbackPath = "/signin-oidc",
            allowedHosts = "*"
        });
    }

    public static TenantProvider Instance()
    {
        lock (_lock)
        {
            if (_instance == null)
            {
                _instance = new TenantProvider();
            }
        }
        
        return _instance;
    }
}

这个Singleton将返回所有我的提供者,以便在我的Program.cs中,我可以像这样添加所有的标识:

foreach(var tenantProvider in TenantProvider.Instance().tenants)
{
    Action<JwtBearerOptions> configureJwtBearerOptions = JWTBearerOptions;
    void JWTBearerOptions(JwtBearerOptions t1)
    {
        t1.Audience = $"{tenantProvider.instance}/{tenantProvider.tenantId}";
        t1.TokenValidationParameters.ValidAudiences = new string[] {
            tenantProvider.clientId,
            $"api://{tenantProvider.clientId}"
        };
    }

    Action<MicrosoftIdentityOptions> configureMicrosoftIdentityOptions = MIOptions;
    void MIOptions(MicrosoftIdentityOptions t2)
    {
        t2.TenantId = tenantProvider.tenantId;
        t2.ClientId = tenantProvider.clientId;
        t2.Instance = tenantProvider.instance;
        t2.Domain = tenantProvider.domain;
    }

    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                  .AddMicrosoftIdentityWebApi(configureJwtBearerOptions,
                                              configureMicrosoftIdentityOptions);
}

当我只有一个提供程序在我的TenantProvider中时,一切都正常工作。
当我激活第二个提供程序时,我会遇到以下错误:
System.InvalidOperationException: 'Scheme already exists: Bearer'

是否有办法在.NET API上实现多个身份提供程序?(多个AD提供程序)(不是B2C)

英文:

I'm building a multi-tenant SaaS application and I need to have multiple ADs as the authenticators to the application.

When i configure one AD at a time, it work 100% fine.
The problem is when i have to initialize multiple MicrosoftIdentity providers.

In this solution, i built a Singleton that will load all the provider information. At the moment, it is mocked like this:

public class TenantProvider
    {
        private static TenantProvider _instance;
        private static readonly object _lock = new object();

        public List&lt;Tenant&gt; tenants { get; set; }

        private TenantProvider() 
        { 
            tenants = new List&lt;Tenant&gt;();

            //mock1
            tenants.Add(new Tenant
            {
                instance = &quot;https://login.microsoftonline.com/&quot;,
                domain = &quot;MyDomain&quot;,
                tenantId = &quot;MyTenantId&quot;,
                clientId = &quot;MyClientId&quot;,
                callbackPath = &quot;/signin-oidc&quot;,
                allowedHosts = &quot;*&quot;
            });

            //mock2
            tenants.Add(new Tenant
            {
                instance = &quot;https://login.microsoftonline.com/&quot;,
                domain = &quot;MyDomain2&quot;,
                tenantId = &quot;MyTenantId2&quot;,
                clientId = &quot;MyClientId2&quot;,
                callbackPath = &quot;/signin-oidc&quot;,
                allowedHosts = &quot;*&quot;
            });
        }

        public static TenantProvider Instance()
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = new TenantProvider();
                }
            }
            
            return _instance;
        }

This Singleton will return all my providers so that in my Program.cs i could add all the identities like in this code:

foreach(var tenantProvider in TenantProvider.Instance().tenants)
{
    Action&lt;JwtBearerOptions&gt; configureJwtBearerOptions = JWTBearerOptions;
    void JWTBearerOptions(JwtBearerOptions t1)
    {
        t1.Audience = $&quot;{tenantProvider.instance}/{tenantProvider.tenantId}&quot;;
        t1.TokenValidationParameters.ValidAudiences = new string[] {
            tenantProvider.clientId,
            $&quot;api://{tenantProvider.clientId}&quot;
        };
    }

    Action&lt;MicrosoftIdentityOptions&gt; configureMicrosoftIdentityOptions = MIOptions;
    void MIOptions(MicrosoftIdentityOptions t2)
    {
        t2.TenantId = tenantProvider.tenantId;
        t2.ClientId = tenantProvider.clientId;
        t2.Instance = tenantProvider.instance;
        t2.Domain = tenantProvider.domain;
    }

    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                  .AddMicrosoftIdentityWebApi(configureJwtBearerOptions,
                                              configureMicrosoftIdentityOptions);
}

When i have only one provider in my TenantProvider, everything works fine.
When i activate the second provider, i have the following error:
System.InvalidOperationException: 'Scheme already exists: Bearer'

Is there a way to implement multiple Identity Providers on the .NET API? (Multiple AD providers) (NOT B2C)

答案1

得分: 1

I actually solved my problem.
我实际解决了我的问题。

I added PolicySchemeOptions, in addition to creating AuthenticationSchemes dynamically, with specific schemeNames for each of AD providers.
我除了动态创建身份验证方案,还添加了PolicySchemeOptions,每个AD提供程序都有特定的schemeNames。

As long as I'm still mocking with personal AD, I have some IFs statements inside the PolicySchemeOption.
只要我还在使用个人AD进行模拟,PolicySchemeOption内部就有一些IF语句。

Here's my actual solution:
这是我的实际解决方案:

foreach (var tp in TenantProvider.Instance().tenants)
{
builder.Services
.AddAuthentication(o => { o.DefaultAuthenticateScheme = "AAD-" + tp.name; o.DefaultChallengeScheme = "AAD-" + tp.name; })

.AddPolicyScheme("AAD-" + tp.name, "AAD-" + tp.name, o =>
{
    o.ForwardDefaultSelector = context =>
    {
        string _providerSelected = string.Empty;
        string authorization = context.Request.Headers[HeaderNames.Authorization];
        if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
        {
            var token = authorization.Substring("Bearer ".Length).Trim();
            var jwtHandler = new JwtSecurityTokenHandler();
            var claims = jwtHandler.ReadJwtToken(token).Claims;

            var email = string.Empty;

            try
            {
                email = claims.FirstOrDefault(x => x.Type == "email").Value;
            }
            catch
            {
                email = claims.FirstOrDefault(x => x.Type == "unique_name").Value;
            }

            MailAddress address = new MailAddress(email);
            var domain = address.Host;

            if (domain == "hotmail.com")
            {
                _providerSelected = "enzo";
            }

            foreach (var tp in TenantProvider.Instance().tenants)
            {
                if (domain.Contains(tp.name))
                {
                    _providerSelected = tp.name; break;
                }
            }
        }
        return _providerSelected;
    };
})

.AddMicrosoftIdentityWebApi(o =>
{

    o.Audience = $"{tp.instance}/{tp.tenantId}";
    o.TokenValidationParameters.ValidAudiences = new string[] {
                tp.clientId,
                $"api://{tp.clientId}"
                };

}, o =>
{
    o.TenantId = tp.tenantId;
    o.ClientId = tp.clientId;
    o.Instance = tp.instance;
    o.Domain = tp.domain;
}, tp.name);

}

Foreach tenant in my tenantProvider, I add a new AuthenticationScheme.
对于我的tenantProvider中的每个租户,我都添加了一个新的AuthenticationScheme。

This way, I can dynamically add how many AD providers I need, and the Authentication works as expected, as long as the user has access to the specified AD.
这样,我可以动态添加需要的AD提供程序,并且只要用户可以访问指定的AD,身份验证就按预期工作。

英文:

I actually solved my problem.
I addedd PolicySchemeOptions, in addition to creating AuthenticationSchemes dynamically, with specific schemeNames for each of AD providers.

As long as I'm still mocking with personal AD, i have some IFs statements inside the PolicySchemeOption

Here's my actual solution:

foreach (var tp in TenantProvider.Instance().tenants)
{
    builder.Services
    .AddAuthentication(o =&gt; { o.DefaultAuthenticateScheme = &quot;AAD-&quot; + tp.name; o.DefaultChallengeScheme = &quot;AAD-&quot; + tp.name; })

    .AddPolicyScheme(&quot;AAD-&quot; + tp.name, &quot;AAD-&quot; + tp.name, o =&gt; {
        o.ForwardDefaultSelector = context =&gt;
        {
            string _providerSelected = string.Empty;
            string authorization = context.Request.Headers[HeaderNames.Authorization];
            if (!string.IsNullOrEmpty(authorization) &amp;&amp; authorization.StartsWith(&quot;Bearer &quot;))
            {
                var token = authorization.Substring(&quot;Bearer &quot;.Length).Trim();
                var jwtHandler = new JwtSecurityTokenHandler();
                var claims = jwtHandler.ReadJwtToken(token).Claims;

                var email = string.Empty;

                try
                {
                    email = claims.FirstOrDefault(x =&gt; x.Type == &quot;email&quot;).Value;
                }
                catch
                {
                    email = claims.FirstOrDefault(x =&gt; x.Type == &quot;unique_name&quot;).Value;
                }

                MailAddress address = new MailAddress(email);
                var domain = address.Host;

                if (domain == &quot;hotmail.com&quot;)
                {
                    _providerSelected = &quot;enzo&quot;;
                }

                foreach (var tp in TenantProvider.Instance().tenants)
                {
                    if (domain.Contains(tp.name))
                    {
                        _providerSelected = tp.name; break;
                    }
                }
            }
            return _providerSelected;
        };
    })

    .AddMicrosoftIdentityWebApi(o =&gt; {

        o.Audience = $&quot;{tp.instance}/{tp.tenantId}&quot;;
        o.TokenValidationParameters.ValidAudiences = new string[] {
                    tp.clientId,
                    $&quot;api://{tp.clientId}&quot;
                    };

    }, o =&gt; {
        o.TenantId = tp.tenantId;
        o.ClientId = tp.clientId;
        o.Instance = tp.instance;
        o.Domain = tp.domain;
    }, tp.name);
}

Foreach tenant in my tenantProvider, i add a new AuthenticationScheme.

This way, i can dynamically add how many AD providers i need and the Authentication works as expected, as long as the user has access to the specified AD.

答案2

得分: 0

After debugging, I found that "Bearer" has been added twice and is causing this exception. Therefore, I want to change the Scheme, as shown in the screenshot below. This should resolve the issue.

添加authenticationScheme到你的Tenant类中,并且修改你的代码如下所示。

public class Tenant
{
    public string? instance { get; set; }
    public string? domain { get; set; }
    public string? tenantId { get; set; }
    public string? clientId { get; set; }
    public string? callbackPath { get; set; }
    public string? allowedHosts { get; set; }
    public string? authenticationScheme { get; set; }
}

Program.cs

foreach (var tenantProvider in TenantProvider.Instance().tenants)
{
    Action<JwtBearerOptions> configureJwtBearerOptions = JWTBearerOptions;
    void JWTBearerOptions(JwtBearerOptions t1)
    {
        t1.Audience = $"{tenantProvider.instance}/{tenantProvider.tenantId}";
        t1.TokenValidationParameters.ValidAudiences = new string[] {
            tenantProvider.clientId,
            $"api://{tenantProvider.clientId}"
        };
    };

    Action<MicrosoftIdentityOptions> configureMicrosoftIdentityOptions = MIOptions;
    void MIOptions(MicrosoftIdentityOptions t2)
    {
        t2.TenantId = tenantProvider.tenantId;
        t2.ClientId = tenantProvider.clientId;
        t2.Instance = tenantProvider.instance;
        t2.Domain = tenantProvider.domain;
    }
    builder.Services.AddAuthentication(tenantProvider.authenticationScheme)
        .AddJwtBearer(tenantProvider.authenticationScheme, configureJwtBearerOptions);

    builder.Services.Configure<JwtBearerOptions>(tenantProvider.authenticationScheme, JWTBearerOptions);

    builder.Services.Configure<MicrosoftIdentityOptions>(tenantProvider.authenticationScheme, MIOptions);

    builder.Services.AddAuthorization(options =>
    {
        options.AddPolicy($"{tenantProvider.authenticationScheme}-policy",
            policy => policy.RequireAuthenticatedUser());
    });
}

Please note that I didn't translate the code portions, as you requested not to translate code.

英文:

After debugging, I found Bearer has add twice and throw this exception, so I want to change the Scheme. Like below screenshot. The issue could be fixed.

Multiple AD authentication schemas – cannot run .NET 7 web API when I add multiple providers.

Multiple AD authentication schemas – cannot run .NET 7 web API when I add multiple providers.

Add authenticationScheme in you Tenant class. And change your code like below.

public class Tenant
{
    public string? instance { get; set; }
    public string? domain { get; set; }
    public string? tenantId { get; set; }
    public string? clientId { get; set; }
    public string? callbackPath { get; set; }
    public string? allowedHosts { get; set; }
    public string? authenticationScheme { get; set; }
}

Program.cs

        foreach (var tenantProvider in TenantProvider.Instance().tenants)
        {
            Action&lt;JwtBearerOptions&gt; configureJwtBearerOptions = JWTBearerOptions;
            void JWTBearerOptions(JwtBearerOptions t1)
            {
                t1.Audience = $&quot;{tenantProvider.instance}/{tenantProvider.tenantId}&quot;;
                t1.TokenValidationParameters.ValidAudiences = new string[] {
                     tenantProvider.clientId,
                     $&quot;api://{tenantProvider.clientId}&quot;
                };
            };

            Action&lt;MicrosoftIdentityOptions&gt; configureMicrosoftIdentityOptions = MIOptions;
            void MIOptions(MicrosoftIdentityOptions t2)
            {
                t2.TenantId = tenantProvider.tenantId;
                t2.ClientId = tenantProvider.clientId;
                t2.Instance = tenantProvider.instance;
                t2.Domain = tenantProvider.domain;
            }
            builder.Services.AddAuthentication(tenantProvider.authenticationScheme)
.AddJwtBearer(tenantProvider.authenticationScheme, configureJwtBearerOptions);

            builder.Services.Configure&lt;JwtBearerOptions&gt;(tenantProvider.authenticationScheme, JWTBearerOptions);

            builder.Services.Configure&lt;MicrosoftIdentityOptions&gt;(tenantProvider.authenticationScheme, MIOptions);

            builder.Services.AddAuthorization(options =&gt;
            {
                options.AddPolicy($&quot;{tenantProvider.authenticationScheme}-policy&quot;,
                    policy =&gt; policy.RequireAuthenticatedUser());
            });

        }

huangapple
  • 本文由 发表于 2023年5月11日 02:44:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76221683.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定