英文:
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<Tenant> tenants { get; set; }
private TenantProvider()
{
tenants = new List<Tenant>();
//mock1
tenants.Add(new Tenant
{
instance = "https://login.microsoftonline.com/",
domain = "MyDomain",
tenantId = "MyTenantId",
clientId = "MyClientId",
callbackPath = "/signin-oidc",
allowedHosts = "*"
});
//mock2
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;
}
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<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);
}
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 => { 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.
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.
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<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());
});
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论