ComponentSpace

Forums



Integrate SAML with IdentityServer4 dynamically


Integrate SAML with IdentityServer4 dynamically

Author
Message
[email protected]
techjerk2013@gmail.com
New Member
New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)

Group: Forum Members
Posts: 3, Visits: 8
Hello there,

The scenario here is, we have centralized a IdentityServer4 that will act as service provider and there are multiple identity providers like Active Directory, Google, Facebook and also other SAML providers based on each tenant. i.e., one service provider and multiple identity providers.

To load the openId configs from database I am exactly following the https://stackoverflow.com/a/56941908/2922388 and it is working as expected for openid and now I need to integrate SAML providers in the same way.

I went through the  "SAMLv20.Core-evaluation" and was able to do the integration using appsettings.json successfully. But I am not sure how to integrate it programatically as given in https://stackoverflow.com/a/56941908/2922388.

Here is what I have done so far

public class AccountController : ControllerBase
{
private readonly IOptionsMonitorCache<OpenIdConnectOptions> _openIdOptionsCache;
private readonly IOptionsMonitorCache<SamlAuthenticationOptions> _samlOptionsCache;
private readonly OpenIdConnectPostConfigureOptions _postConfigureOptions;
private readonly SamlPostConfigureAuthenticationOptions _samlPostConfigureOptions;

public AccountController(
IOptionsMonitorCache<OpenIdConnectOptions> openidOptionsCache,
IOptionsMonitorCache<SamlAuthenticationOptions> samlOptionsCache,
OpenIdConnectPostConfigureOptions postConfigureOptions,
SamlPostConfigureAuthenticationOptions samlPostConfigureOptions
)
{
_openIdOptionsCache = openidOptionsCache;
_samlOptionsCache = samlOptionsCache;
_postConfigureOptions = postConfigureOptions;
_samlPostConfigureOptions = samlPostConfigureOptions;
}

private async Task<IEnumerable<AuthenticationScheme>> LoadAuthenticationSchemesByTenant(IEnumerable<AuthenticationScheme> schemes, AuthProviderSetting tenantAuthProviderSetting)
{
dynamic configJson = JsonConvert.DeserializeObject(tenantAuthProviderSetting.tenantConfigJson);
switch (tenantAuthProviderSetting.AuthenticationType)
{
case AuthenticationTypes.OpenID:
var oidcOptions = new OpenIdConnectOptions
{
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
SignOutScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
SaveTokens = true,
Authority = configJson.Authority,
ClientId = configJson.ClientId,
ClientSecret = configJson.ClientSecret,

TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role",
ValidateIssuer = false
}
};
_schemeProvider.AddScheme(new AuthenticationScheme(tenantAuthProviderSetting.AuthenticationScheme, tenantAuthProviderSetting.DisplayName, typeof(OpenIdConnectHandler)));
_postConfigureOptions.PostConfigure(tenantAuthProviderSetting.AuthenticationScheme, oidcOptions);
_openIdOptionsCache.TryAdd(tenantAuthProviderSetting.AuthenticationScheme, oidcOptions);
schemes = await _schemeProvider.GetAllSchemesAsync();
break;

case AuthenticationTypes.SAML:
var samlOptions = new SamlAuthenticationOptions
{

PartnerName = delegate () { return "https://ExampleIdentityProvider"; },
SingleLogoutServicePath = "https://localhost:44313/SAML/SingleLogoutService",

// Not sure how to set other parameters here

};

_schemeProvider.AddScheme(new AuthenticationScheme(tenantAuthProviderSetting.AuthenticationScheme, tenantAuthProviderSetting.DisplayName, typeof(SamlAuthenticationHandler)));
_samlPostConfigureOptions.PostConfigure(tenantAuthProviderSetting.AuthenticationScheme, samlOptions);
_samlOptionsCache.TryAdd(tenantAuthProviderSetting.AuthenticationScheme, samlOptions);
schemes = await _schemeProvider.GetAllSchemesAsync();
break;
default:
schemes = await _schemeProvider.GetAllSchemesAsync();
break;

}
return schemes;
}
}

Here is the config through which I used to statically integrate with IdentityServer

"SAML": {
  "$schema": "https://www.componentspace.com/schemas/saml-config-schema-v1.0.json",
  "Configurations": [
   {
    "LocalServiceProviderConfiguration": {
    "Name": "https://IdentityServer4",
    "Description": "IdentityServer4",
    "AssertionConsumerServiceUrl": "http://localhost:44380/SAML/AssertionConsumerService",
    "SingleLogoutServiceUrl": "http://localhost:44380/SAML/SingleLogoutService",
    "LocalCertificates": [
     {
      "FileName": "certificates/sp.pfx",
      "Password": "password"
     }
    ]
    },
    "PartnerIdentityProviderConfigurations": [
    {
     "Name": "https://ExampleIdentityProvider",
     "Description": "Example Identity Provider",
     "SignAuthnRequest": true,
     "SingleSignOnServiceUrl": "https://localhost:44313/SAML/SingleSignOnService",
     "SingleLogoutServiceUrl": "https://localhost:44313/SAML/SingleLogoutService",
     "PartnerCertificates": [
      {
      "FileName": "certificates/idp.cer"
      }
     ]
    }
    ]
   }
  ]
  },
  "PartnerName": "https://ExampleIdentityProvider"

I've also raised the same in stackoverflow (https://stackoverflow.com/questions/59646417/load-dynamic-saml-schemes-for-identityserver4-using-componentspace) as well.



ComponentSpace
ComponentSpace
ComponentSpace Development
ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)

Group: Administrators
Posts: 3.2K, Visits: 11K
Just to confirm, you want to add the SAML configuration dynamically?

The best way to do this is to implement the ISamlConfigurationResolver as described in the "Implementing ISamlConfigurationResolver" section of our Configuration Guide.

https://www.componentspace.com/Forums/8234/Configuration-Guide

Your implementation of ISamlConfigurationResolver is called whenever configuration is required. This means the SAML configuration is entirely dynamic.

Regards
ComponentSpace Development
[email protected]
techjerk2013@gmail.com
New Member
New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)

Group: Forum Members
Posts: 3, Visits: 8
Thanks for the reply.

I've updated the Startup as

 services.AddTransient<ISamlConfigurationResolver, CustomSamlConfigurationResolver>();
services.AddSaml();
services.AddAuthentication().AddSaml();

and implemented ISamlConfigurationResolver as given below

public class CustomSamlConfigurationResolver : AbstractSamlConfigurationResolver, 
  {
   public override Task<IList<string>> GetPartnerIdentityProviderNamesAsync(string configurationID)
   {
    IList<string> partnerNames = new List<string>() { "https://ExampleIdentityProvider" };
    return Task.FromResult(partnerNames);
   }

   public override Task<PartnerIdentityProviderConfiguration> GetPartnerIdentityProviderConfigurationAsync(string configurationID, string partnerName)
   {
    var partnerIdentityProviderConfiguration = new PartnerIdentityProviderConfiguration()
    {
      Name = "https://ExampleIdentityProvider",
      Description = "Example Identity Provider",
      SignAuthnRequest = true,
      SingleSignOnServiceUrl = "https://localhost:44313/SAML/SingleSignOnService",
      SingleLogoutServiceUrl = "https://localhost:44313/SAML/SingleLogoutService",
      PartnerCertificates = new List<Certificate>()
       {
        new Certificate()
        {
          FileName = "certificates/idp.cer"
        }
       }
    }; return Task.FromResult(partnerIdentityProviderConfiguration);
   }


   public override Task<LocalServiceProviderConfiguration> GetLocalServiceProviderConfigurationAsync(string configurationID)
   {
    var localServiceProviderConfiguration = new LocalServiceProviderConfiguration()
    {
      Name = "https://IdentityServer4",
      Description = "IdentityServer4",
      AssertionConsumerServiceUrl = "http://localhost:44380/SAML/AssertionConsumerService",
      SingleLogoutServiceUrl = "http://localhost:44380/SAML/SingleLogoutService",
      LocalCertificates = new List<Certificate>()
       {
       new Certificate()
        { FileName = "certificates/sp.pfx",
          Password = "password"
        }
       }
    };
    return Task.FromResult(localServiceProviderConfiguration);
   }
  }

It looks like the override methods are hooked to execution pipeline and is called by framework. Since we need to fetch the config from the database at some different execution point, how to ensure  GetLocalServiceProviderConfigurationAsync and GetPartnerIdentityProviderConfigurationAsync is called at our convenience rather than framework invoking the methods automatically? 

Also, we would like to name the provider with TenantName rather than calling it as "SAML" always. How do we do it?




ComponentSpace
ComponentSpace
ComponentSpace Development
ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)

Group: Administrators
Posts: 3.2K, Visits: 11K
The ISamlConfigurationResolver implementation is called just-in-time to fetch the required SAML configuration. In other words, we call ISamlConfigurationResolver as required as part of SSO or SLO. This ensures we have the very latest SAML configuration.

Many implementations store the SAML configuration in a custom database. The ISamlConfigurationResolver implementation reads from this database as requested.

The other option is to read the configuration and set this through the SAML configuration API. This should be done at application start-up. However, in most scenarios implementing ISamlConfigurationResolver provides more flexibility.

More details may be found in our Configuration Guide.

https://www.componentspace.com/Forums/8234/Configuration-Guide

By the provider name, do you mean the authentication handler's display name?

Assuming so, the display name, which defaults to SAML, may be specified through the authentication handler arguments.


using ComponentSpace.Saml2.Authentication;

services.AddAuthentication().AddSaml(
    SamlAuthenticationDefaults.AuthenticationScheme,
    "DISPLAY-NAME-GOES-HERE",
    options =>
{
  options.PartnerName = () => Configuration["PartnerName"];
});




Regards
ComponentSpace Development
[email protected]
techjerk2013@gmail.com
New Member
New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)New Member (4 reputation)

Group: Forum Members
Posts: 3, Visits: 8
I don't want to set the "Authentication handler's display name"  while adding saml services.AddAuthentication().AddSaml()  but I need to set while implementing ISamlConfigurationResolver 

So, how do I set the "Authentication handler's display name" while implementing ISamlConfigurationResolver?



ComponentSpace
ComponentSpace
ComponentSpace Development
ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)ComponentSpace Development (4.4K reputation)

Group: Administrators
Posts: 3.2K, Visits: 11K
That's not possible. The SAML authentication handler follows the same pattern set out by Microsoft for all authentication handlers. The AuthenticationBuilder.AddScheme method only supports specifying the display name as a string. This occurs when the authentication scheme and associated authentication handler are registered.

https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.authenticationbuilder.addscheme?view=aspnetcore-3.1


Regards
ComponentSpace Development
GO


Similar Topics


Execution: 0.000. 2 queries. Compression Enabled.
Login
Existing Account
Email Address:


Password:


Select a Forum....












Forums, Documentation & Knowledge Base - ComponentSpace


Search