configurationName is NULL

I’m using ComponentSpace v5.0.0
I have a multi-tenant application so I’m setting the configurationName to then obtaing it in my custom class SamlConfigurationResolver, the implementation has been working fine for months, but in the last two weeks I have expereicned an issue by wich the configurationName is arriving as (null) on GetLocalServiceProviderConfigurationAsync(), the issue starts suddenly and gone after recycling its IIS app pool.
here is the code:

builder.Services.AddSaml();
builder.Services.AddTransient<ISamlConfigurationResolver, SamlConfigurationResolver>();

[HttpPost(“/{customerId}/saml/login”)]
public async Task LoginSaml(string customerId)
{
return !ModelState.IsValid
? BadRequest()
: await LoginSamlPost(customerId);
}

private async Task LoginSamlPost(string customerId)
{
await _samlServiceProvider.SetConfigurationNameAsync(customerId);
ssoResult = await _samlServiceProvider.ReceiveSsoAsync();
}

public class SamlConfigurationResolver : ISamlConfigurationResolver
{
public Task GetLocalServiceProviderConfigurationAsync(string configurationName = null)
{
string customerId = configurationName;
	try
	{
		string localCertificate = GetLocalCertificate(customerId);

		HttpRequest request = _httpContextAccessor.HttpContext.Request;
		string requestUrl = string.Concat("https://", request.Host.ToUriComponent(), request.Path.ToUriComponent());

		LocalServiceProviderConfiguration localServiceProviderConfiguration = new()
		{
			Name = localCertificate,
			AssertionConsumerServiceUrl = requestUrl,
			LocalCertificates =
			[
				new()
				{
					SubjectName = localCertificate
				}
			]
		};
		return Task.FromResult(localServiceProviderConfiguration);
	}
	catch (Exception ex)
	{
		string errorMessage = $"An error occurred while retrieving the SDP configuration: {ex.Message}";
		throw new ArgumentException(errorMessage);
	}
}

public Task<PartnerIdentityProviderConfiguration> GetPartnerIdentityProviderConfigurationAsync(string configurationName = null, string partnerName = null)
{
	string customerId = configurationName;
	
	if (configurationName != partnerName)
	{
		_logger.LogWarning("partnerName: '{PartnerName}', and configurationName: {ConfigurationName} values are not the same.", partnerName, configurationName);
		throw new ArgumentException("Parameters provided in the Saml configuration do not match.");
	}
	try
	{
		string digestAlgorithm = GetDigestAgorith(customerId);
		string signatureAlgorithm = GetSignatureAlgorithm(customerId);
		string partnerCertificate = GetPartnerCertificate(customerId);

		PartnerIdentityProviderConfiguration partnerIdentityProviderConfiguration = new()
		{
			Name = partnerName,
			PartnerCertificates =
			[
				new()
				{
					String = partnerCertificate
				}
			],
			WantDigestAlgorithm = digestAlgorithm,
			WantSignatureAlgorithm = signatureAlgorithm
		};

		return Task.FromResult(partnerIdentityProviderConfiguration);
	}
	catch (Exception ex)
	{
		string errorMessage = $"An error occurred while retrieving the IDP configuration: {ex.Message}";
		throw new ArgumentException(errorMessage);
	}

}

}

I suggest adding some test logic in your LoginSamlPost method to confirm the customerId isn’t null.

If that looks ok, it’s possible the SAML session state, which is where the configuration name is saved, is being lost. This state is indexed by a saml-session cookie which is marked as Secure and SameSite=None. It’s possible this cookie isn’t being sent by the browser under certain conditions. This can be investigated using the browser developer tools.

It’s interesting that recycling the app pool resolves the issue.

If the issue can be reproduced easily, I suggest enabling SAML trace and sending the log file, along with a reference to your forum post, to support@componentspace.com.

I noticed in ComponentSpace code that if for some reason the saml-session cookie is not sent the code generates a new one and uses it to save the saml session state.
I verified the cookie is being sent, but additionally I intentionally removed that cookie, but the code on DistributedSsoSessionStore generated a new one and the state was saved and then retrieved successfully:

public virtual string SessionID
{
get
{
if (string.IsNullOrEmpty(sessionID))
{
if (request.Cookies.ContainsKey(distributedSsoSessionStoreOptions.CookieName))
{
sessionID = request.Cookies[distributedSsoSessionStoreOptions.CookieName];
logger.LogDebug($“The SSO session ID {sessionID} has been retrieved from the {distributedSsoSessionStoreOptions.CookieName} cookie.”);
}
else
{
string cookieValue = Guid.NewGuid().ToString();
AddCookie(distributedSsoSessionStoreOptions.CookieName, cookieValue, distributedSsoSessionStoreOptions.CookieOptions);
sessionID = cookieValue;
logger.LogDebug($“The SSO session ID {sessionID} has been saved to the {distributedSsoSessionStoreOptions.CookieName} cookie.”);
LogUtility.Log(logger, distributedSsoSessionStoreOptions.CookieName, cookieValue, distributedSsoSessionStoreOptions.CookieOptions);
}
}
return sessionID;
}
}

That’s correct. If there’s no saml-session cookie we create one.

I’m assuming this is an intermittent issue so perhaps it’s hard to reproduce.

If possible, it would be good to confirm whether the cookie is sent when the issue occurs.

It’s difficult to know if the cookie is being sent or not, in our UAT environment it’s no possible to reproduce the issue, it only happens in PROD, and as I said it was working fine for months or even years.. the issue started suddenly weeks ago, but every time it happens it’s solved by recycling its IIS app pool.

That being said, what do you suggest to solve the issue?

I was thinking to add logic at GetLocalServiceProviderConfigurationAsync and GetPartnerIdentityProviderConfigurationAsync, to get configurationName from HttpContext as a fallback in case the parameter arrives as null, what do you think?

var configurationName = _httpContextAccessor.HttpContext?
.GetRouteValue("configurationName")?
.ToString();

Given this had been working for sometime, it’s unlikely to be the SAML library and is more likely to be some external change. Also, it’s very curious that a recycle of the app pool resolves the issue.

I think your idea of retrieving the configuration name via the HttpContext is a good idea.

This might also shed some light on what’s causing the issue.

Let me know how you go.