SSO User State Management

Hello,

Per your examples for ASP.NET Core with SAML SSO, you verify whether user has been authenticated using the HttpContext. This will work only for the SP that authenticated but will ask other SPs to authenticate again even if it’s within the same browser session.

Can you please recommend from your expertise and knowledge with SAML common ways to manage the user state through different SPs?

Much appreciated.

Hello again,

Apologies this was a mistake from my side. This will work among multiple different SPs within the same browser since the HttpContext.SignInAsync() most likely stores some kind of cookie into the browser session.

The issue I was facing was due to actually signing in again from within the SP on SAML assertion consumption. The point of this was because we have a requirement where we actually need to allow SPs to logout from just their SP without firing SLO that terminates the IdP state. In the end, the HttpContext.SignoutAsync() will remove the cookie regardless from where it’s done from the IdP or SP side.

Do you have any suggestions on how to proceed with this kind of requirement?


Edit: I am facing another problem as well where SP-Initiated SLOs with multiple SPs leading to wrong redirections. Logging out from SP-A would redirect me to SP-B after logging out and vice versa.

Sharing authentication state between SPs is not covered by the SAML specification. If you have multiple SPs under the same domain and using the same authentication cookie this can be achieved.

There is no requirement to initiate SLO on local logout. Don’t call InitiateSloAsync if you don’t wish to initiate SLO.

If you’re seeing wrong redirections on SLO, the most likely cause is a configuration issue. Make sure the correct single logout endpoints are configured for the various SPs.

If there’s still an issue, please enable SAML trace and send the generated log files to support@componentspace.com mentioning your forum post. Include the logs for the IdP and the SPs.

https://www.componentspace.com/forums/7936/Enabling-SAML-Trace

[quote]
ComponentSpace - 2/22/2024
Sharing authentication state between SPs is not covered by the SAML specification. If you have multiple SPs under the same domain and using the same authentication cookie this can be achieved.

There is no requirement to initiate SLO on local logout. Don't call InitiateSloAsync if you don't wish to initiate SLO.

If you're seeing wrong redirections on SLO, the most likely cause is a configuration issue. Make sure the correct single logout endpoints are configured for the various SPs.

If there's still an issue, please enable SAML trace and send the generated log files to support@componentspace.com mentioning your forum post. Include the logs for the IdP and the SPs.

https://www.componentspace.com/forums/7936/Enabling-SAML-Trace

[/quote]





Hello,

Thank you for the quick response. Regarding the redirection, I made sure the configurations were correct. I traced down the request of SP-Initiated SLO and came to notice that your library sends a logout response back to active SsoSessions over InResponseTo which is leading to this behavior.

Please note this only happens after two SPs are logged in and works completely normal if only one SP is logged. For example, I would try logging out from SP-B and IdP would redirect back to SP-A logout endpoint over SP-A because it’s in the SsoSession list.

The IdP is responsible for sending a logout request to the other SPs with active sessions.

The flow at the IdP is:

1. IdP receives logout request from SP-B.
2. IdP sends logout request to SP-A.
3. IdP receives logout response from SP-A.
4. IdP sends logout request to SP-C.
5. IdP receives logout response from SP-C.
6. IdP sends logout response to SP-B.

I see. So the first SP sends a InitiateSloAsync().The requests coming from IdP to the SPs after would technically be received at the SingleSignoutService of the SP that will receive the SLO request and then send response back to Idp with SendSloAsync (correct if I am wrong here).

And in the end, it will respond back to the first initiating caller for them to redirect locally. I would assume here only this last call would switch the .IsResponseTo bool to true and the other SPs would have it as false.

Your understanding is correct.

The API calls at the IdP are:

1. ReceiveSloAsync to receive a logout request from SP-B. ISloResult.IsResponse is false.
2. SendSloAsync to send a logout request to SP-A.
3. ReceiveSloAsync to receive a logout response from SP-A and to send a logout request to SP-C. ISloResult.IsResponse is true. ISloResult.HasCompleted is false.
4. ReceiveSloAsync to receive a logout response from SP-C and to send a logout response to SP-B. ISloResult.IsResponse is true. ISloResult.HasCompleted is true.

This pattern is demonstrated in the ExampleIdentityProvider’s SamlController.SingleLogoutService method.

I recommend following this pattern.

I have implemented the changes needed above but for some reason the IdP is throwing an exception of no pending SPs to respond to on ISamlIdentityProvider.SendSloAsync(). I feel like the session is being lost since in the first SLO initiation we do have an active SSO session and another pending one.

Here is the code I am using:

SP:
public async ValueTask SingleSignoutService()
{
var sloResult = await _samlServiceProvider.ReceiveSloAsync();
if (sloResult.IsResponse)
{
_stateService.RemoveItem(“UserDetails”);
return View(“/Views/ServiceProvider/Index.cshtml”);
}
_stateService.RemoveItem(“UserDetails”);
await _samlServiceProvider.SendSloAsync();
return new EmptyResult();
}

IdP:
public async ValueTask SingleSignoutService()
{
var sloResult = await _samlIdentityProvider.ReceiveSloAsync();

if (sloResult.IsResponse && sloResult.HasCompleted)
RedirectToAction(“Index”);

await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);

await _samlIdentityProvider.SendSloAsync(); //throws here on second time

return new EmptyResult();
}

I think your IdP logic is a little off. If sloResult.IsResponse is true but sloResult.HasCompleted is false you simply want to return new EmptyResult().

Here’s the login from the ExampleIdentityProvider.


// Receive the single logout request or response.
// If a request is received then single logout is being initiated by a partner SP.
// If a response is received then this is in response to single logout having been initiated by the IdP.
var sloResult = await _samlIdentityProvider.ReceiveSloAsync();

if (sloResult.IsResponse)
{
if (sloResult.HasCompleted)
{
// IdP-initiated SLO has completed.
if (!string.IsNullOrEmpty(sloResult.RelayState))
{
return LocalRedirect(sloResult.RelayState);
}

return RedirectToPage(“/Index”);
}
}
else
{
// Logout locally.
await _signInManager.SignOutAsync();

// Respond to the SP-initiated SLO request indicating successful logout.
await _samlIdentityProvider.SendSloAsync();
}

return new EmptyResult();