SAML Single Log Out issue

We are having some issue with SLO. ComponentSpace.SAML2.dll we are using is version 2.4.0.11, that we purchased a few years ago. When I looked at the developer guide online http://www.componentspace.com/Documentation/SAML%20v2.0%20Developer%20Guide.pdf, it is showing the following example for idP-Initiated SLO:

5.1.4 SAMLIdentityProvider.InitiateSLOThe InitiateSLO method sends a logout request to each service provider in session as partof IdP-initiated SLO.For example: SAMLIdentityProvider.InitiateSLO( Response, null);…

However, in the the library we are using, I couldn’t find SAMLIdentityProvider.InitiateSLO. I can only find SingleLogoutService.SendLogoutRequestByHTTPPost. So we implemented using SingleLogoutService, but it doesn’t work correctly. The request doesn’t get posted at all and no error shows in the the log file neither. Below is the c# code we are using to implement SLO.

A La Carte and Feature Direct Reference Guide public void SendSAMLSLORequest(string sessionId)
{
MetadataReader spReader = new MetadataReader(string.Format(SPMetadata, env));
spReader.Process();

MetadataReader idpReader = new MetadataReader(string.Format(IDPMetadata, env));
idpReader.Process();

LogoutRequest samlRequest = CreateSAMLRequest(spReader, idpReader, sessionId);
SendSAMLRequest(samlRequest, spReader, idpReader, sessionId);

}
private LogoutRequest CreateSAMLRequest(MetadataReader spReader, MetadataReader idpReader, string sessionIndex)
{
LogoutRequest result = new LogoutRequest();
try
{
result.NameID = new NameID(client.OpaqueId);
result.Destination = spReader.SingleLogOutServiceUrl;
result.Issuer = new Issuer(idpReader.EntityId);
result.IssueInstant = DateTime.UtcNow;
result.NotOnOrAfter = DateTime.UtcNow.AddMinutes(10);
result.Reason = “IDP Logout”;
result.SessionIndexes = new List();
SessionIndex session = new SessionIndex(sessionIndex);
result.SessionIndexes.Add(session);
}
catch (Exception ex)
{

}

return result;
}

private void SendSAMLRequest(LogoutRequest samlSLORequest, MetadataReader spReader, MetadataReader idpReader, string sessionIndex)
{
XmlElement samlRequestXml = samlSLORequest.ToXml();
if (idpReader.SigningCert != null && idpReader.SigningCert.PrivateKey != null)
{
SAMLMessageSignature.Generate(samlRequestXml, idpReader.SigningCert.PrivateKey, idpReader.SigningCert);
SingleLogoutService.SendLogoutRequestByHTTPPost(Response, spReader.SingleLogOutServiceUrl, samlRequestXml, null);

}
else
{

}
}

Does anyone have ideas what is wrong with our SLO implementation? How can we use SAMLIdentityProvider.InitiateSLO? Do we need to upgrade our library?

thanks in advance!

SAMLIdentityProvider.InitiateSLO is part of a new high-level API we introduced in 2013. It sounds like you are using an earlier version of the product.
Calling SingleLogoutService.SendLogoutRequestByHTTPPost should work. This is part of the original low-level API.
Please confirm that SingleLogoutService.SendLogoutRequestByHTTPPost is being called.
Where do you end up in the browser?
Also, please use something like Fiddler to capture the HTTP traffic to see what is being returned.
Do you see the HTTP Post being sent to the service provider?

Where do you end up in the browser? Are you at your site or the partner site?
Also, please use something like Fiddler to capture the HTTP traffic.
Do you see the HTTP Post being sent to the partner provider?

Hi,

I also use this method and enable the diagnostics logs. It seems that it post to the url but in the fiddler i cannot find the url.

Is there any other code in your method that could be overriding the HTTP response containing the logout request?

Using Fiddler or the browser developer tools (F12), what’s the HTTP response being returned by your application?

[quote]
ComponentSpace - 10/29/2020
Is there any other code in your method that could be overriding the HTTP response containing the logout request?

Using Fiddler or the browser developer tools (F12), what's the HTTP response being returned by your application?
[/quote]

After the logout request is done, i do a Response.redirect to another page. How do I get back the logoutresponse from SendLogoutRequestViaHttpPost? Currently the sending of request did not throw any errors and page is redirected successfully.

The Response.Redirect replaces the 200 HTTP response we’re attempting to return with your 302 redirect HTTP response. This explains why you don’t see the SAML logout request being sent.

SendLogoutRequestViaHttpPost will send a SAML logout request, via the browser, to the partner provider. Control is now at the partner provider website. After the user is logged out at the partner provider site they should send a SAML logout response to your logout service endpoint. You now have control back at your website and would call our API to receive and process the logout response. You can then redirect to the appropriate page as SAML logout has now completed.

[quote]
ComponentSpace - 10/29/2020
The Response.Redirect replaces the 200 HTTP response we're attempting to return with your 302 redirect HTTP response. This explains why you don't see the SAML logout request being sent.

SendLogoutRequestViaHttpPost will send a SAML logout request, via the browser, to the partner provider. Control is now at the partner provider website. After the user is logged out at the partner provider site they should send a SAML logout response to your logout service endpoint. You now have control back at your website and would call our API to receive and process the logout response. You can then redirect to the appropriate page as SAML logout has now completed.
[/quote]

What API should I call to receive and process the logout response? Do you have a sample code for it? I prefer using the low level api than high level api. I tried using it dotnet HttpWebRequest to get response, i am able to get 200 OK but there is no output in the body when I read the responsestream. Does it mean by the partner provider site have already received it and processed it?

I recommend you use the SAML high-level API as it’s much easier to use.

However, if you wish to use the low-level API you should call either SingleLogoutService.ReceiveLogoutResponseByHTTPPost or SingleLogoutService.ReceiveLogoutResponseByHTTPRedirect, depending on whether the SAML logout response is sent over HTTP-Post or HTTP-Redirect.

The SAML2ServiceProvider project, which you’ll find under the Examples folder, includes a SAML/SLOService.aspx page that demonstrates calling SingleLogoutService.SendLogoutResponseByHTTPRedirect and SingleLogoutService.ReceiveLogoutMessageByHTTPRedirect.

The ReceiveLogoutMessageByHTTPRedirect method supports receiving either a SAML logout request or response. You can call ReceiveLogoutResponseByHTTPRedirect if you know you’re expecting a logout response rather than a logout request.

[quote]
ComponentSpace - 10/29/2020
I recommend you use the SAML high-level API as it's much easier to use.

However, if you wish to use the low-level API you should call either SingleLogoutService.ReceiveLogoutResponseByHTTPPost or SingleLogoutService.ReceiveLogoutResponseByHTTPRedirect, depending on whether the SAML logout response is sent over HTTP-Post or HTTP-Redirect.

The SAML2ServiceProvider project, which you'll find under the Examples folder, includes a SAML/SLOService.aspx page that demonstrates calling SingleLogoutService.SendLogoutResponseByHTTPRedirect and SingleLogoutService.ReceiveLogoutMessageByHTTPRedirect.

The ReceiveLogoutMessageByHTTPRedirect method supports receiving either a SAML logout request or response. You can call ReceiveLogoutResponseByHTTPRedirect if you know you're expecting a logout response rather than a logout request.
[/quote]

Hi, I call the SingleLogoutService.SendLogoutRequestByHTTPPost to the sample project SAML2ServiceProvider SLOService.aspx but the logs did not hit on the Page_Load

This is what I diagnosed on the send logout request
http://www.w3.org/1999/xhtml"><body onload="document.forms.samlform.submit()">

Note: Since your browser does not support Javascript, you must press the Continue button once to proceed.

https://localhost:3443/samlSP/SAML/SLOService.aspx" method="post" target="_self">


What I expected is that it should hit the Page_Load at the /samlSP/SAML/SLOService.aspx but there is no logs being write out. I have change the web.config to point to my log directory.


The SAML logout request will be sent to https://localhost:3443/samlSP/SAML/SLOService.aspx.

If you’re not seeing this page hit, where do you end up in the browser (ie what’s the URL in the address bar)?

Try the browser developer tools (F12) to capture the network traffic to see what’s happening.

[quote]
ComponentSpace - 10/30/2020
The SAML logout request will be sent to https://localhost:3443/samlSP/SAML/SLOService.aspx.

If you're not seeing this page hit, where do you end up in the browser (ie what's the URL in the address bar)?

Try the browser developer tools (F12) to capture the network traffic to see what's happening.
[/quote]

I did a Response.redirect after I send the logout request post to samlSP. Therefore I cannot capture the traffic to there.
So I change the code not to redirect and in the browser developer tool ,it capture my post request to samlSP but the response capture is just 200 OK and and no logout response.
there is no logs in samlSP too.

Removing the Response.Redirect is correct. You can do a redirect and send a SAML logout request in the same HTTP response.

Are you saying you’re seeing an HTTP Post with the logout request sent to https://localhost:3443/samlSP/SAML/SLOService.aspx but no SAML logout response is being returned?

Have you tried setting a breakpoint in /SLOService.aspx to see what happens with the logout request?

[quote]
ComponentSpace - 10/30/2020
Removing the Response.Redirect is correct. You can do a redirect and send a SAML logout request in the same HTTP response.

Are you saying you're seeing an HTTP Post with the logout request sent to https://localhost:3443/samlSP/SAML/SLOService.aspx but no SAML logout response is being returned?

Have you tried setting a breakpoint in /SLOService.aspx to see what happens with the logout request?
[/quote]

I will need a Response.redirect eventually as there might be multiple logout requests i need to do.

Yes. From the developer tools in chrome, it stated that I did a post with the request data SAMLRequest. But at the response tab, it did not have the SAML response.

I am using IIS to host the samlSP. In its web.config, i change log path for text tracelistener. In the sample code, it do a trace.write when hit page_load so it should be able to hit.
[quote]
ComponentSpace - 10/30/2020
Removing the Response.Redirect is correct. You can do a redirect and send a SAML logout request in the same HTTP response.

Are you saying you're seeing an HTTP Post with the logout request sent to https://localhost:3443/samlSP/SAML/SLOService.aspx but no SAML logout response is being returned?

Have you tried setting a breakpoint in /SLOService.aspx to see what happens with the logout request?
[/quote]

OK, so I run the SAML2ServiceProvider in debug mode at Visual Studio. It reaches the Page_Load and throw an exception "Exception: ComponentSpace.SAML2.Exceptions.SAMLBindingException: No SAML message query string parameter in HTTP Redirect".

I uses SingleLogoutService.SendLogoutRequestByHTTPPost to do a POST to SAML2ServiceProvider
[quote]
ComponentSpace - 10/30/2020
Removing the Response.Redirect is correct. You can do a redirect and send a SAML logout request in the same HTTP response.

Are you saying you're seeing an HTTP Post with the logout request sent to https://localhost:3443/samlSP/SAML/SLOService.aspx but no SAML logout response is being returned?

Have you tried setting a breakpoint in /SLOService.aspx to see what happens with the logout request?
[/quote]

Hi,

I manage to solve the issue. Since I still need the Response.redirect, I use alternative solution to solve it and get the logout response.

Steps:

1. Generate LogoutRequest XML element using ComponentSpace
2. Use HttpWebRequest to send form data POST request (passing in the URL and the LogoutRequest.OuterXML())

Below is the code:
public string postData(string destinationUrl, string requestXml)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(destinationUrl);

string base64data = Base64Encode(requestXml);
string postdata = "SAMLRequest=" + HttpUtility.UrlEncode(base64data);

byte[] bytes;
bytes = Encoding.ASCII.GetBytes(postdata);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = bytes.Length;
request.Method = "POST";
Stream requestStream = request.GetRequestStream();
requestStream.Write(bytes, 0, bytes.Length);
requestStream.Close();
HttpWebResponse response;
response = (HttpWebResponse)request.GetResponse();

log("postData destinationurl: " + destinationUrl + ", statuscode: " + response.StatusCode);

if (response.StatusCode == HttpStatusCode.OK)
{
Stream responseStream = response.GetResponseStream();
string responseStr = new StreamReader(responseStream).ReadToEnd();
return responseStr;
}
return null;
}

At SAML2ServiceProvider side,

I change the code at Page_Load to use SingleLogoutService.ReceiveLogoutMessageByHTTPPost instead of SingleLogoutService.ReceiveLogoutMessageByHTTPRedirect.
Then at SendLogoutResponse(), i comment out the usage of SingleLogoutService.SendLogoutResponseByHTTPRedirect and change to Response.Write(logoutResponseXml.OuterXml). So that responseStr will be able to get the logoutresponse:
http://localhost:51394/<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />


Hopefully, this can help other people who have similar issues.

Thanks :)
[quote]
ComponentSpace - 10/30/2020
Removing the Response.Redirect is correct. You can do a redirect and send a SAML logout request in the same HTTP response.

Are you saying you're seeing an HTTP Post with the logout request sent to https://localhost:3443/samlSP/SAML/SLOService.aspx but no SAML logout response is being returned?

Have you tried setting a breakpoint in /SLOService.aspx to see what happens with the logout request?
[/quote]

I will need a Response.redirect eventually as there might be multiple logout requests i need to do.

Yes. From the developer tools in chrome, it stated that I did a post with the request data SAMLRequest. But at the response tab, it did not have the SAML response.

I am using IIS to host the samlSP. In its web.config, i change log path for text tracelistener. In the sample code, it do a trace.write when hit page_load so it should be able to hit.[/quote]
You would perform after SAML logout completes. The sequence is: send a logout request, receive a logout response, redirect.
[quote]
ComponentSpace - 10/30/2020
Removing the Response.Redirect is correct. You can do a redirect and send a SAML logout request in the same HTTP response.

Are you saying you're seeing an HTTP Post with the logout request sent to https://localhost:3443/samlSP/SAML/SLOService.aspx but no SAML logout response is being returned?

Have you tried setting a breakpoint in /SLOService.aspx to see what happens with the logout request?
[/quote]

OK, so I run the SAML2ServiceProvider in debug mode at Visual Studio. It reaches the Page_Load and throw an exception "Exception: ComponentSpace.SAML2.Exceptions.SAMLBindingException: No SAML message query string parameter in HTTP Redirect".

I uses SingleLogoutService.SendLogoutRequestByHTTPPost to do a POST to SAML2ServiceProvider[/quote]
The SAML2ServiceProvider is expecting to receive the logout request using the HTTP-Redirect binding rather than the HTTP-Post binding. You could modify the code to call ReceiveLogoutMessageByHTTPPost instead of ReceiveLogoutMessageByHTTPRedirect.
[quote]
ComponentSpace - 10/30/2020
Removing the Response.Redirect is correct. You can do a redirect and send a SAML logout request in the same HTTP response.

Are you saying you're seeing an HTTP Post with the logout request sent to https://localhost:3443/samlSP/SAML/SLOService.aspx but no SAML logout response is being returned?

Have you tried setting a breakpoint in /SLOService.aspx to see what happens with the logout request?
[/quote]

Hi,

I manage to solve the issue. Since I still need the Response.redirect, I use alternative solution to solve it and get the logout response.

Steps:

1. Generate LogoutRequest XML element using ComponentSpace
2. Use HttpWebRequest to send form data POST request (passing in the URL and the LogoutRequest.OuterXML())

Below is the code:
public string postData(string destinationUrl, string requestXml)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(destinationUrl);

string base64data = Base64Encode(requestXml);
string postdata = "SAMLRequest=" + HttpUtility.UrlEncode(base64data);

byte[] bytes;
bytes = Encoding.ASCII.GetBytes(postdata);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = bytes.Length;
request.Method = "POST";
Stream requestStream = request.GetRequestStream();
requestStream.Write(bytes, 0, bytes.Length);
requestStream.Close();
HttpWebResponse response;
response = (HttpWebResponse)request.GetResponse();

log("postData destinationurl: " + destinationUrl + ", statuscode: " + response.StatusCode);

if (response.StatusCode == HttpStatusCode.OK)
{
Stream responseStream = response.GetResponseStream();
string responseStr = new StreamReader(responseStream).ReadToEnd();
return responseStr;
}
return null;
}

At SAML2ServiceProvider side,

I change the code at Page_Load to use SingleLogoutService.ReceiveLogoutMessageByHTTPPost instead of SingleLogoutService.ReceiveLogoutMessageByHTTPRedirect.
Then at SendLogoutResponse(), i comment out the usage of SingleLogoutService.SendLogoutResponseByHTTPRedirect and change to Response.Write(logoutResponseXml.OuterXml). So that responseStr will be able to get the logoutresponse:
http://localhost:51394/http://localhost:51394/http://localhost:51394/<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />


Hopefully, this can help other people who have similar issues.

Thanks :)[/quote]
The recommended approach, if using the SAML low-level API, is to call SendLogoutRequestByHTTPRedirect or SendLogoutRequestByHTTPPost to send the logout request and ReceiveLogoutMessageByHTTPRedirect or ReceiveLogoutMessageByHTTPPost to receive the logout request. What you did obviously worked but is more code than is required.