Setting the configuration in a multi-tenanted application by issuer

Hi,

We’re a service provider. Generally we’ve been providing out partner Identity Providers a unique URL for our assertion consumer service. That URL route contains a configuration ID that we’ve been using to set the configuration before we receive the assertion from the IdP.

We now have a requirement that wants us to have the same entityId and acs endpoint for all IdP’s. That means no more unique route that tells me the configuration ID to start.

My questions,
We need call SetConfigurationIDAsync before we call ReceiveSsoAsync, correct?
ReceiveSsoAsync will fail if the correct corresponding configuration is not set, correct?
If we don’t have an ID to set the configuration, is there a way to set the configuration by the Issuer of the incoming message?

Unless there’s something built in I have a feeling I’ll have to read the query myself and try to get the issuer out of the saml message. Then I can query our db for the configuration and set it myself.

Thanks!


If the entityID and ACS endpoint are now the same for all IdPs, perhaps you don’t need the multi-tenanted support. You can have a single SAML configuration with one local SP configuration and multiple partner IdP configurations. That way you don’t have to call SetConfigurationIDAsync and you avoid the issue.

If you do require multiple SAML configurations but you can no longer use the URL to identify the configuration to specify when your call SetConfigurationIDAsync, you need some other way to make this determination. You could decode the SAML message and take a look at the issuer but it’s an unusual thing to do.

Let me know why you still need multi-tenanted support with separate SAML configurations rather than one SAML configuration with multiple partner IdPs.

If I understand correctly, loading up the configurations happens at startup. We could potentially have hundreds or more of these configurations to load up from the database to start up the app.

We also want to have the IdP’s set up their own configuration. Once an IdP saves their configuration they may not be able to log in just yet. We’ve got multiple instances of the app running. If we call the update method on the configurations when an IdP is added one pod could have the latest configuration loaded while another wouldn’t.

That’s why I think we need to continue setting the configuration on the fly. We also save some other extra stuff along with it so we still need to make a database call during an acs.

I’d love to stop setting the configuration on every saml request/response but it may not be feasible unless I’m missing something.

Am I completely wrong or do you think decoding the message would be our best option?

How are you specifying all the SAML configurations now? Are you calling something like “services.AddSaml(config => ConfigureSaml(config))” at application start-up?

If you’re storing SAML configuration in a database, I recommend implementing the ISamlConfigurationResolver interface. Instead of all the SAML configuration being loaded at application start-up, we call into this interface to retrieve specific SAML configuration when it’s required (eg at the start of an SSO flow). This means that we pick up any configuration changes immediately.

Just to recap, if your LocalServiceProviderConfiguration is the same regardless of the partner IdP then I recommend that you only use a single SamlConfiguration consisting of one LocalServiceProviderConfiguration and multiple PartnerIdentityProviderConfiguration objects. Supporting multiple SamlConfiguration objects is intended for multi-tenancy configurations where the LocalServiceProviderConfiguration is different for each tenant.

If there’s a single SamlConfiguration, you don’t call SetConfigurationIDAsync.

To support dynamic additions and change to the SAML configuration stored in your database, I recommend implementing the ISamlConfigurationResolver interface so these changes are picked up immediately.

Doing the above would avoid having to decode SAML messages to determine the issuer.

Part of the route to our endpoints contains an ID number. The IdP is assigned one of these numbers and then I call SetConfigurationId on the number. I’ve implemented the resolver to pick up the configuration based on that configurationId in the route and it calls GetPartnerIdentityProviderConfigurationAsync when I call SetConfigurationID. Does it not need to do that anymore? How do you know which configuration to load up without an ID?

Here’s the acs endpoint

[HttpPost(“saml/{configurationId}/acs”)]
public async Task AssertionConsumerService(string configurationId)
{
try
{
await _samlServiceProvider.SetConfigurationIDAsync(configurationId);

Here’s the resolver
public override async Task GetPartnerIdentityProviderConfigurationAsync(string configurationID, string partnerName)
{
var idpConfig= await _idpService.GetById(long.Parse(configurationID));
if (idpConfig is null)
throw new InvalidOperationException($“Partner Identity Provider Configuration [{configurationID}] not found”);
return Map(idpConfig);
}

public override async Task GetLocalServiceProviderConfigurationAsync(string configurationID)
{
var spConfig= await _spService.GetLocalServiceProviderConfiguration(long.Parse(configurationID));
return Map(spConfig);
}


I can have this return the same SP configuration every time and remove the configurationID from the route but how do you know which IdP configuration to load up? Will it just work because I don’t actually need to load the configuration of the IdP anymore?

The GetPartnerIdentityProviderConfigurationAsync takes two parameters - configurationID and partnerName.

The configurationID identifies the particular SAML configuration to use. Remember, a SAML configuration consists of one LocalServiceProviderConfiguration and multiple PartnerIdentityProviderConfiguration objects. If there’s only one SAML configuration, you don’t call SetConfigurationIDAsync and the configurationID parameter to GetPartnerIdentityProviderConfigurationAsync will be null.

The partnerName identifies the PartnerIdentityProviderConfiguration. The partnerName is the Issuer field in the SAML message from the IdP. It should be matched on the PartnerIdentityProviderConfiguration.Name.

So your GetPartnerIdentityProviderConfigurationAsync implementation would ignore the null configurationID and use the partnerName to lookup the partner identity provider configuration in your database.

GetLocalServiceProviderConfigurationAsync would return the one and only local service provider configuration from your database.

This all assumes that the local service provider configuration is the same regardless of the partner identity provider. This sounds like it would be the case if your service provider’s name (ie entityID) and assertion consumer service URL are the same for all partner identity providers.