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

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

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,它将加载所有提供程序的信息。目前,它是这样模拟的:

  1. public class TenantProvider
  2. {
  3. private static TenantProvider _instance;
  4. private static readonly object _lock = new object();
  5. public List<Tenant> tenants { get; set; }
  6. private TenantProvider()
  7. {
  8. tenants = new List<Tenant>();
  9. //模拟1
  10. tenants.Add(new Tenant
  11. {
  12. instance = "https://login.microsoftonline.com/",
  13. domain = "MyDomain",
  14. tenantId = "MyTenantId",
  15. clientId = "MyClientId",
  16. callbackPath = "/signin-oidc",
  17. allowedHosts = "*"
  18. });
  19. //模拟2
  20. tenants.Add(new Tenant
  21. {
  22. instance = "https://login.microsoftonline.com/",
  23. domain = "MyDomain2",
  24. tenantId = "MyTenantId2",
  25. clientId = "MyClientId2",
  26. callbackPath = "/signin-oidc",
  27. allowedHosts = "*"
  28. });
  29. }
  30. public static TenantProvider Instance()
  31. {
  32. lock (_lock)
  33. {
  34. if (_instance == null)
  35. {
  36. _instance = new TenantProvider();
  37. }
  38. }
  39. return _instance;
  40. }
  41. }

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

  1. foreach(var tenantProvider in TenantProvider.Instance().tenants)
  2. {
  3. Action<JwtBearerOptions> configureJwtBearerOptions = JWTBearerOptions;
  4. void JWTBearerOptions(JwtBearerOptions t1)
  5. {
  6. t1.Audience = $"{tenantProvider.instance}/{tenantProvider.tenantId}";
  7. t1.TokenValidationParameters.ValidAudiences = new string[] {
  8. tenantProvider.clientId,
  9. $"api://{tenantProvider.clientId}"
  10. };
  11. }
  12. Action<MicrosoftIdentityOptions> configureMicrosoftIdentityOptions = MIOptions;
  13. void MIOptions(MicrosoftIdentityOptions t2)
  14. {
  15. t2.TenantId = tenantProvider.tenantId;
  16. t2.ClientId = tenantProvider.clientId;
  17. t2.Instance = tenantProvider.instance;
  18. t2.Domain = tenantProvider.domain;
  19. }
  20. builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  21. .AddMicrosoftIdentityWebApi(configureJwtBearerOptions,
  22. configureMicrosoftIdentityOptions);
  23. }

当我只有一个提供程序在我的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:

  1. public class TenantProvider
  2. {
  3. private static TenantProvider _instance;
  4. private static readonly object _lock = new object();
  5. public List&lt;Tenant&gt; tenants { get; set; }
  6. private TenantProvider()
  7. {
  8. tenants = new List&lt;Tenant&gt;();
  9. //mock1
  10. tenants.Add(new Tenant
  11. {
  12. instance = &quot;https://login.microsoftonline.com/&quot;,
  13. domain = &quot;MyDomain&quot;,
  14. tenantId = &quot;MyTenantId&quot;,
  15. clientId = &quot;MyClientId&quot;,
  16. callbackPath = &quot;/signin-oidc&quot;,
  17. allowedHosts = &quot;*&quot;
  18. });
  19. //mock2
  20. tenants.Add(new Tenant
  21. {
  22. instance = &quot;https://login.microsoftonline.com/&quot;,
  23. domain = &quot;MyDomain2&quot;,
  24. tenantId = &quot;MyTenantId2&quot;,
  25. clientId = &quot;MyClientId2&quot;,
  26. callbackPath = &quot;/signin-oidc&quot;,
  27. allowedHosts = &quot;*&quot;
  28. });
  29. }
  30. public static TenantProvider Instance()
  31. {
  32. lock (_lock)
  33. {
  34. if (_instance == null)
  35. {
  36. _instance = new TenantProvider();
  37. }
  38. }
  39. return _instance;
  40. }

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

  1. foreach(var tenantProvider in TenantProvider.Instance().tenants)
  2. {
  3. Action&lt;JwtBearerOptions&gt; configureJwtBearerOptions = JWTBearerOptions;
  4. void JWTBearerOptions(JwtBearerOptions t1)
  5. {
  6. t1.Audience = $&quot;{tenantProvider.instance}/{tenantProvider.tenantId}&quot;;
  7. t1.TokenValidationParameters.ValidAudiences = new string[] {
  8. tenantProvider.clientId,
  9. $&quot;api://{tenantProvider.clientId}&quot;
  10. };
  11. }
  12. Action&lt;MicrosoftIdentityOptions&gt; configureMicrosoftIdentityOptions = MIOptions;
  13. void MIOptions(MicrosoftIdentityOptions t2)
  14. {
  15. t2.TenantId = tenantProvider.tenantId;
  16. t2.ClientId = tenantProvider.clientId;
  17. t2.Instance = tenantProvider.instance;
  18. t2.Domain = tenantProvider.domain;
  19. }
  20. builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  21. .AddMicrosoftIdentityWebApi(configureJwtBearerOptions,
  22. configureMicrosoftIdentityOptions);
  23. }

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

  1. .AddPolicyScheme("AAD-" + tp.name, "AAD-" + tp.name, o =>
  2. {
  3. o.ForwardDefaultSelector = context =>
  4. {
  5. string _providerSelected = string.Empty;
  6. string authorization = context.Request.Headers[HeaderNames.Authorization];
  7. if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith("Bearer "))
  8. {
  9. var token = authorization.Substring("Bearer ".Length).Trim();
  10. var jwtHandler = new JwtSecurityTokenHandler();
  11. var claims = jwtHandler.ReadJwtToken(token).Claims;
  12. var email = string.Empty;
  13. try
  14. {
  15. email = claims.FirstOrDefault(x => x.Type == "email").Value;
  16. }
  17. catch
  18. {
  19. email = claims.FirstOrDefault(x => x.Type == "unique_name").Value;
  20. }
  21. MailAddress address = new MailAddress(email);
  22. var domain = address.Host;
  23. if (domain == "hotmail.com")
  24. {
  25. _providerSelected = "enzo";
  26. }
  27. foreach (var tp in TenantProvider.Instance().tenants)
  28. {
  29. if (domain.Contains(tp.name))
  30. {
  31. _providerSelected = tp.name; break;
  32. }
  33. }
  34. }
  35. return _providerSelected;
  36. };
  37. })
  38. .AddMicrosoftIdentityWebApi(o =>
  39. {
  40. o.Audience = $"{tp.instance}/{tp.tenantId}";
  41. o.TokenValidationParameters.ValidAudiences = new string[] {
  42. tp.clientId,
  43. $"api://{tp.clientId}"
  44. };
  45. }, o =>
  46. {
  47. o.TenantId = tp.tenantId;
  48. o.ClientId = tp.clientId;
  49. o.Instance = tp.instance;
  50. o.Domain = tp.domain;
  51. }, 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:

  1. foreach (var tp in TenantProvider.Instance().tenants)
  2. {
  3. builder.Services
  4. .AddAuthentication(o =&gt; { o.DefaultAuthenticateScheme = &quot;AAD-&quot; + tp.name; o.DefaultChallengeScheme = &quot;AAD-&quot; + tp.name; })
  5. .AddPolicyScheme(&quot;AAD-&quot; + tp.name, &quot;AAD-&quot; + tp.name, o =&gt; {
  6. o.ForwardDefaultSelector = context =&gt;
  7. {
  8. string _providerSelected = string.Empty;
  9. string authorization = context.Request.Headers[HeaderNames.Authorization];
  10. if (!string.IsNullOrEmpty(authorization) &amp;&amp; authorization.StartsWith(&quot;Bearer &quot;))
  11. {
  12. var token = authorization.Substring(&quot;Bearer &quot;.Length).Trim();
  13. var jwtHandler = new JwtSecurityTokenHandler();
  14. var claims = jwtHandler.ReadJwtToken(token).Claims;
  15. var email = string.Empty;
  16. try
  17. {
  18. email = claims.FirstOrDefault(x =&gt; x.Type == &quot;email&quot;).Value;
  19. }
  20. catch
  21. {
  22. email = claims.FirstOrDefault(x =&gt; x.Type == &quot;unique_name&quot;).Value;
  23. }
  24. MailAddress address = new MailAddress(email);
  25. var domain = address.Host;
  26. if (domain == &quot;hotmail.com&quot;)
  27. {
  28. _providerSelected = &quot;enzo&quot;;
  29. }
  30. foreach (var tp in TenantProvider.Instance().tenants)
  31. {
  32. if (domain.Contains(tp.name))
  33. {
  34. _providerSelected = tp.name; break;
  35. }
  36. }
  37. }
  38. return _providerSelected;
  39. };
  40. })
  41. .AddMicrosoftIdentityWebApi(o =&gt; {
  42. o.Audience = $&quot;{tp.instance}/{tp.tenantId}&quot;;
  43. o.TokenValidationParameters.ValidAudiences = new string[] {
  44. tp.clientId,
  45. $&quot;api://{tp.clientId}&quot;
  46. };
  47. }, o =&gt; {
  48. o.TenantId = tp.tenantId;
  49. o.ClientId = tp.clientId;
  50. o.Instance = tp.instance;
  51. o.Domain = tp.domain;
  52. }, tp.name);
  53. }

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类中,并且修改你的代码如下所示。

  1. public class Tenant
  2. {
  3. public string? instance { get; set; }
  4. public string? domain { get; set; }
  5. public string? tenantId { get; set; }
  6. public string? clientId { get; set; }
  7. public string? callbackPath { get; set; }
  8. public string? allowedHosts { get; set; }
  9. public string? authenticationScheme { get; set; }
  10. }

Program.cs

  1. foreach (var tenantProvider in TenantProvider.Instance().tenants)
  2. {
  3. Action<JwtBearerOptions> configureJwtBearerOptions = JWTBearerOptions;
  4. void JWTBearerOptions(JwtBearerOptions t1)
  5. {
  6. t1.Audience = $"{tenantProvider.instance}/{tenantProvider.tenantId}";
  7. t1.TokenValidationParameters.ValidAudiences = new string[] {
  8. tenantProvider.clientId,
  9. $"api://{tenantProvider.clientId}"
  10. };
  11. };
  12. Action<MicrosoftIdentityOptions> configureMicrosoftIdentityOptions = MIOptions;
  13. void MIOptions(MicrosoftIdentityOptions t2)
  14. {
  15. t2.TenantId = tenantProvider.tenantId;
  16. t2.ClientId = tenantProvider.clientId;
  17. t2.Instance = tenantProvider.instance;
  18. t2.Domain = tenantProvider.domain;
  19. }
  20. builder.Services.AddAuthentication(tenantProvider.authenticationScheme)
  21. .AddJwtBearer(tenantProvider.authenticationScheme, configureJwtBearerOptions);
  22. builder.Services.Configure<JwtBearerOptions>(tenantProvider.authenticationScheme, JWTBearerOptions);
  23. builder.Services.Configure<MicrosoftIdentityOptions>(tenantProvider.authenticationScheme, MIOptions);
  24. builder.Services.AddAuthorization(options =>
  25. {
  26. options.AddPolicy($"{tenantProvider.authenticationScheme}-policy",
  27. policy => policy.RequireAuthenticatedUser());
  28. });
  29. }

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.

  1. public class Tenant
  2. {
  3. public string? instance { get; set; }
  4. public string? domain { get; set; }
  5. public string? tenantId { get; set; }
  6. public string? clientId { get; set; }
  7. public string? callbackPath { get; set; }
  8. public string? allowedHosts { get; set; }
  9. public string? authenticationScheme { get; set; }
  10. }

Program.cs

  1. foreach (var tenantProvider in TenantProvider.Instance().tenants)
  2. {
  3. Action&lt;JwtBearerOptions&gt; configureJwtBearerOptions = JWTBearerOptions;
  4. void JWTBearerOptions(JwtBearerOptions t1)
  5. {
  6. t1.Audience = $&quot;{tenantProvider.instance}/{tenantProvider.tenantId}&quot;;
  7. t1.TokenValidationParameters.ValidAudiences = new string[] {
  8. tenantProvider.clientId,
  9. $&quot;api://{tenantProvider.clientId}&quot;
  10. };
  11. };
  12. Action&lt;MicrosoftIdentityOptions&gt; configureMicrosoftIdentityOptions = MIOptions;
  13. void MIOptions(MicrosoftIdentityOptions t2)
  14. {
  15. t2.TenantId = tenantProvider.tenantId;
  16. t2.ClientId = tenantProvider.clientId;
  17. t2.Instance = tenantProvider.instance;
  18. t2.Domain = tenantProvider.domain;
  19. }
  20. builder.Services.AddAuthentication(tenantProvider.authenticationScheme)
  21. .AddJwtBearer(tenantProvider.authenticationScheme, configureJwtBearerOptions);
  22. builder.Services.Configure&lt;JwtBearerOptions&gt;(tenantProvider.authenticationScheme, JWTBearerOptions);
  23. builder.Services.Configure&lt;MicrosoftIdentityOptions&gt;(tenantProvider.authenticationScheme, MIOptions);
  24. builder.Services.AddAuthorization(options =&gt;
  25. {
  26. options.AddPolicy($&quot;{tenantProvider.authenticationScheme}-policy&quot;,
  27. policy =&gt; policy.RequireAuthenticatedUser());
  28. });
  29. }

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:

确定