How can I set the Authority on OpenIdConnect middleware options dynamically?

后端 未结 2 1746
南笙
南笙 2020-12-03 08:59

We have multiple tenants, and they use different authorities (their own, not just standard providers). While I know how to dynamically set the clientId and secret, I can\'t

相关标签:
2条回答
  • 2020-12-03 09:22

    While a bit tricky, it's definitely possible. Here's a simplified example, using the MSFT OIDC handler, a custom monitor and path-based tenant resolution:

    Implement your tenant resolution logic. E.g:

    public class TenantProvider
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
    
        public TenantProvider(IHttpContextAccessor httpContextAccessor)
            => _httpContextAccessor = httpContextAccessor;
    
        public string GetCurrentTenant()
        {
            // This sample uses the path base as the tenant.
            // You can replace that by your own logic.
            string tenant = _httpContextAccessor.HttpContext.Request.PathBase;
            if (string.IsNullOrEmpty(tenant))
            {
                tenant = "default";
            }
    
            return tenant;
        }
    }
    
    public void Configure(IApplicationBuilder app)
    {
        app.Use(next => context =>
        {
            // This snippet uses a hardcoded resolution logic.
            // In a real world app, you'd want to customize that.
            if (context.Request.Path.StartsWithSegments("/fabrikam", out PathString path))
            {
                context.Request.PathBase = "/fabrikam";
                context.Request.Path = path;
            }
    
            return next(context);
        });
    
        app.UseAuthentication();
    
        app.UseMvc();
    }
    

    Implement a custom IOptionsMonitor<OpenIdConnectOptions>:

    public class OpenIdConnectOptionsProvider : IOptionsMonitor<OpenIdConnectOptions>
    {
        private readonly ConcurrentDictionary<(string name, string tenant), Lazy<OpenIdConnectOptions>> _cache;
        private readonly IOptionsFactory<OpenIdConnectOptions> _optionsFactory;
        private readonly TenantProvider _tenantProvider;
    
        public OpenIdConnectOptionsProvider(
            IOptionsFactory<OpenIdConnectOptions> optionsFactory,
            TenantProvider tenantProvider)
        {
            _cache = new ConcurrentDictionary<(string, string), Lazy<OpenIdConnectOptions>>();
            _optionsFactory = optionsFactory;
            _tenantProvider = tenantProvider;
        }
    
        public OpenIdConnectOptions CurrentValue => Get(Options.DefaultName);
    
        public OpenIdConnectOptions Get(string name)
        {
            var tenant = _tenantProvider.GetCurrentTenant();
    
            Lazy<OpenIdConnectOptions> Create() => new Lazy<OpenIdConnectOptions>(() => _optionsFactory.Create(name));
            return _cache.GetOrAdd((name, tenant), _ => Create()).Value;
        }
    
        public IDisposable OnChange(Action<OpenIdConnectOptions, string> listener) => null;
    }
    

    Implement a custom IConfigureNamedOptions<OpenIdConnectOptions>:

    public class OpenIdConnectOptionsInitializer : IConfigureNamedOptions<OpenIdConnectOptions>
    {
        private readonly IDataProtectionProvider _dataProtectionProvider;
        private readonly TenantProvider _tenantProvider;
    
        public OpenIdConnectOptionsInitializer(
            IDataProtectionProvider dataProtectionProvider,
            TenantProvider tenantProvider)
        {
            _dataProtectionProvider = dataProtectionProvider;
            _tenantProvider = tenantProvider;
        }
    
        public void Configure(string name, OpenIdConnectOptions options)
        {
            if (!string.Equals(name, OpenIdConnectDefaults.AuthenticationScheme, StringComparison.Ordinal))
            {
                return;
            }
    
            var tenant = _tenantProvider.GetCurrentTenant();
    
            // Create a tenant-specific data protection provider to ensure
            // encrypted states can't be read/decrypted by the other tenants.
            options.DataProtectionProvider = _dataProtectionProvider.CreateProtector(tenant);
    
            // Other tenant-specific options like options.Authority can be registered here.
        }
    
        public void Configure(OpenIdConnectOptions options)
            => Debug.Fail("This infrastructure method shouldn't be called.");
    }
    

    Register the services in your DI container:

    public void ConfigureServices(IServiceCollection services)
    {
        // ...
    
        // Register the OpenID Connect handler.
        services.AddAuthentication()
            .AddOpenIdConnect();
    
        services.AddSingleton<TenantProvider>();
        services.AddSingleton<IOptionsMonitor<OpenIdConnectOptions>, OpenIdConnectOptionsProvider>();
        services.AddSingleton<IConfigureOptions<OpenIdConnectOptions>, OpenIdConnectOptionsInitializer>();
    }
    
    0 讨论(0)
  • 2020-12-03 09:26

    The Asp.NET Core model assumes one upstream authority per handler instance. My Saml2 component supports multiple upstream Idps in one handler and it has drawbacks in the rest of the system when that assumption no longer is true.

    In Asp.NET Core it is possible to add/remove providers at runtime, without requiring a restart. So I'd recommend finding a model based on that.

    If you rather want one handler that can have a per-request Authority setting, I think that a custom handler is needed - Microsoft's default implementation won't support that.

    0 讨论(0)
提交回复
热议问题