Edit:
After struggling for a long time to figure this out, I came across a potential solution. As of today (2021-10-19), the latest stable version of System.ServiceModel.*** packages is 4.8.1, but there are release candidates for 4.9.0 which seem to solve exactly the problem I'm having here.
I checked the .NET WCF GitHub source and found this release candidate (version 4.9.0-rc1.21431.2) which has exactly what I'm looking for. They've updated the HttpTransportBindingElement to include a Proxy property. Obviously it is not stable release yet, but it still gets the job done. With that I was able to solve the original problem using something that looks like this:
using (var myWsdlClient = new MyWsdlGeneratedClient())
{
var binding = myWsdlClient.Endpoint.Binding as BasicHttpBinding;
var customBinding = new CustomBinding(binding);
var htbe = customBinding.Elements.Find<HttpTransportBindingElement>();
htbe.AuthenticationScheme = AuthenticationSchemes.Basic;
htbe.ProxyAuthenticationScheme = AuthenticationSchemes.Basic;
htbe.UseDefaultWebProxy = false;
htbe.BypassProxyOnLocal = false;
htbe.Proxy = new WebProxy
{
Address = new Uri("http://myproxyaddress.com:8080"),
/* Proxy creds */
Credentials = new NetworkCredential("MyProxyUserName", "MyProxyPassword"),
BypassProxyOnLocal = false
};
myWsdlClient.Endpoint.Binding = customBinding;
/* Client creds */
myWsdlClient.ClientCredentials.UserName.UserName = "MyClientUserName";
myWsdlClient.ClientCredentials.UserName.Password = "MyClientPassword";
/* Send request */
myWsdlClient.Endpoint.Address = new EndpointAddress("https://myclientaddress.com");
myWsdlClient.doSomeAction(actionRequest); // <-- IT WORKS!!!
}
Original question:
I'm trying to send a WCF service request through a web proxy, and I'm receiving the error "Remote Server returned an error: (407) Proxy Authentication Required". I've already generated the proxy classes with a WSDL, set up the bindings/endpoints etc. in my app.config (it is a BasicHttpBinding). The problem is: both the client and the proxy require Basic authentication, and I can only seem be able to set the client credentials, not the proxy.
Things I've already tried:
I saw online you could try to pass credentials in the URL of the proxy itself. So I did this programatically for the ProxyAddress property on the binding, like so:
using (var myWsdlClient = new MyWsdlGeneratedClient())
{
var binding = myWsdlClient.Endpoint.Binding as BasicHttpBinding;
/* Client creds */
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
myWsdlClient.ClientCredentials.UserName.UserName = "MyClientUserName";
myWsdlClient.ClientCredentials.UserName.Password = "MyClientPassword";
/* Proxy creds */
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.Basic;
binding.UseDefaultWebProxy = false;
binding.BypassProxyOnLocal = false;
binding.ProxyAddress = new Uri("http://MyProxyUserName:MyProxyPassword#myproxyaddress.com:8080");
/* Send request */
myWsdlClient.Endpoint.Address = new EndpointAddress("https://myclientaddress.com");
myWsdlClient.doSomeAction(actionRequest); // <-- error is thrown here, inner exception is 407 HTTP response
}
I also tried with default web proxy (it sorta worked). Again, I set it programatically like so:
using (var myWsdlClient = new MyWsdlGeneratedClient())
{
var binding = myWsdlClient.Endpoint.Binding as BasicHttpBinding;
/* Client creds */
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
myWsdlClient.ClientCredentials.UserName.UserName = "MyClientUserName";
myWsdlClient.ClientCredentials.UserName.Password = "MyClientPassword";
/* Proxy creds */
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.Basic;
binding.UseDefaultWebProxy = true;
binding.BypassProxyOnLocal = false;
var defaultProxyBefore = WebRequest.DefaultWebProxy;
var newProxy = new WebProxy
{
Address = new Uri("http://myproxyaddress.com:8080"),
Credentials = new NetworkCredential("MyProxyUserName", "MyProxyPassword"),
BypassProxyOnLocal = false
};
WebRequest.DefaultWebProxy = newProxy;
/* Send request */
myWsdlClient.Endpoint.Address = new EndpointAddress("https://myclientaddress.com");
try
{
myWsdlClient.doSomeAction(actionRequest);
}
finally
{
WebRequest.DefaultWebProxy = defaultProxyBefore;
}
}
The good thing about this second approach is that it actually worked! However, it is not enough for the requirements of my project. The application I am developing is sending loads of requests per second on different threads, some of which are going through the default proxy. I don't want all those unrelated requests to go through my "new" proxy, they should continue to go through the default.
So to summarize, I need a way of setting the proxy per-request, while also being able to set Basic authentication for both the client and the proxy. I'm not very experienced with WCF and I have just stumbled along the concept of "Custom bindings", which seems promising, but I still haven't found if it can do what I need. Any help on this is incredibly appreciated!
Welcome to Stack Overflow. Thanks for your detailed question.
The "proper" solution is to use an HTTPS proxy (not an HTTP proxy).
If this isn't feasible, you can set the Binding's security mode to BasicHttpSecurityMode.TransportCredentialOnly. (Because Basic Authentication isn't encrypted, I don't recommend doing this in a Production application.)
Below is an example based on your original post. Let me know if it works for you.
using (var myWsdlClient = new MyWsdlGeneratedClient())
{
var binding = myWsdlClient.Endpoint.Binding as BasicHttpBinding;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
/* Client creds */
myWsdlClient.ClientCredentials.UserName.UserName = "MyClientUserName";
myWsdlClient.ClientCredentials.UserName.Password = "MyClientPassword";
/* Disable HTTPS requirement */
binding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
/* Proxy creds */
/*
* Since the credentials for the Proxy are in the URL,
* set the proxy credential type to None (the default value).
* Otherwise, WCF may attempt using myWsdlClient.ClientCredentials to
* authenticate with the Proxy.
*/
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
/* Note: UseDefaultWebProxy is true by default. */
binding.UseDefaultWebProxy = false;
binding.BypassProxyOnLocal = false;
/* Ensure your Proxy Server supports passing credentials in the URL. */
binding.ProxyAddress = new Uri("http://MyProxyUserName:MyProxyPassword#myproxyaddress.com:8080");
/* Send request */
myWsdlClient.Endpoint.Address = new EndpointAddress("https://myclientaddress.com");
myWsdlClient.doSomeAction(actionRequest);
}
Related
I have a .NET Core 2.0 application and need to call a WCF client from one of its controllers, and pass the user credentials for authentication.
Within the .net core app I created a reference for the WCF client using the Connected Services (WCF Web Service Reference Provider) and now in a process of configuring the call. Note that I can use the same endpoint form a 4.6 framework application without any problems.
Here's my code:
var binding = new BasicHttpBinding {Security = {Mode = BasicHttpSecurityMode.Transport}};
var address = new EndpointAddress("https://my-endpoint.asmx");
var client = new MyAppSoapClient(binding, address);
var credentials = CredentialCache.DefaultNetworkCredentials;
client.ClientCredentials.Windows.ClientCredential = credentials;
client.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
var response = client.GetStuff("param").Result;
I face a number of problems:
It has to be a https call
I need to pass the currently log in user credentials to the call
The current error I get is as follows:
The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Negotiate, NTLM'
Also the ConnectedService.json (created automativcally by WCF Web Service Reference Provider) has a predefined endpoint Uri.. I don't understand why I need to pass the address to the client manually (the code seems to be forcing me to do so).. ideally I'd like to get this dynamically amended in json depending on environment.
Thanks.
I noticed that you passed the current logged-in user as a Windows credential (which is also necessary for enabling impersonation), but you did not explicitly set the client credentials for the transport layer security.
BasicHttpBinding binding = new BasicHttpBinding();
binding.Security.Mode = BasicHttpSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Windows;
Also the ConnectedService.json (created automativcally by WCF Web
Service Reference Provider) has a predefined endpoint Uri.. I don't
understand why I need to pass the address to the client manually (the
code seems to be forcing me to do so)
You can modify the method of automatic generation of proxy client to construct client proxy class (located in the reference.cs)
Modify the binding security
private static System.ServiceModel.Channels.Binding GetBindingForEndpoint(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.WebService1Soap))
{
System.ServiceModel.BasicHttpBinding result = new System.ServiceModel.BasicHttpBinding();
result.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.Transport;
result.Security.Transport.ClientCredentialType = System.ServiceModel.HttpClientCredentialType.Windows;
result.MaxBufferSize = int.MaxValue;
result.ReaderQuotas = System.Xml.XmlDictionaryReaderQuotas.Max;
result.MaxReceivedMessageSize = int.MaxValue;
result.AllowCookies = true;
return result;
}
Modify the endpoint.
private static System.ServiceModel.EndpointAddress GetEndpointAddress(EndpointConfiguration endpointConfiguration)
{
if ((endpointConfiguration == EndpointConfiguration.WebService1Soap))
{
return new System.ServiceModel.EndpointAddress("http://10.157.13.69:8001/webservice1.asmx");
Construct the client proxy class.
ServiceReference1.WebService1SoapClient client = new WebService1SoapClient(WebService1SoapClient.EndpointConfiguration.WebService1Soap);
client.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
client.ClientCredentials.Windows.ClientCredential.UserName = "administrator";
client.ClientCredentials.Windows.ClientCredential.Password = "123456";
Feel free to let me know if there is anything I can help with.
My binding was missing the security Ntlm credential type (see below).
Problem solved.
var binding = new BasicHttpBinding {Security = {Mode = BasicHttpSecurityMode.Transport,
Transport = new HttpTransportSecurity(){ClientCredentialType = HttpClientCredentialType.Ntlm } }};
I coded a WCF Service using HttpTransportBindingElement in conjunction with IIS on port 80.
The code works fine as long as no proxy is used. But if a customer has a http-proxy the communication between WCF-Client and Server does not work in this case by occuring following error:
'There was no endpoint listening at ... that could accept the message. This is often caused by an incorrect address or SOAP action.'
It is essential to use settings by code ONLY!
here is my code approach for that issue but i stuck on it:
bool SendClientRequest(Action<ICustomerService> channel)
{
string proxy ="my.proxy.domain:8080";
string user = "user1";
string password="secret";
// maybe i do not need this 3 lines!
WebProxy webproxy = new WebProxy(proxy, true);
webproxy.Credentials = new NetworkCredential(user, password);
WebRequest.DefaultWebProxy = webproxy;
CustomBinding customBinding = new CustomBinding();
customBinding.Elements.Add(new HttpTransportBindingElement()
{
AuthenticationSchemes.None : AuthenticationSchemes.Basic,
ProxyAddress = string.IsNullOrEmpty(proxy) ? null : new Uri(proxy),
UseDefaultWebProxy = false,
BypassProxyOnLocal = true,
TransferMode = TransferMode.Streamed,
MaxReceivedMessageSize = 84087406592,
MaxBufferPoolSize = 0x1000000,
MaxBufferSize = 0x1000000
});
using (ChannelFactory<ICustomerService> factory = new
ChannelFactory<ICustomerService>(customBinding ))
{
IClientChannel contextChannel = null;
string url = "http://my.domain.de/Distribution/eService.svc",
EndpointAddress ep = new EndpointAddress(url);
ICustomerService clientChannel = factory.CreateChannel(ep);
contextChannel = clientChannel as IClientChannel;
contextChannel.OperationTimeout = TimeSpan.FromMinutes(rcvTimeout );
channel(clientChannel); // <- here i get the exception!
return true;
}
}
I tried several solution approaches but nothing seems to be specific like mine.
I think you have a few options, some of which I'll detail below.
First you could set UseDefaultWebProxy to true. This would then mean that proxy information is retrieved automatically from system proxy settings, configurable in Internet Explorer (Internet Options > Connections > LAN settings > Proxy server). This may be appropriate if you don't need to specify credentials for proxy use.
Another approach that's worked for me is to use the ProxyAuthenticationScheme property within your HttpTransportBindingElement() object. This property is only available on the CustomBinding class and allows an authentication scheme to be specified that will be used to authenticate against a proxy. In conjunction with this, the proxy server must be set against property ProxyAddress. Last but not least, the credentials to use against the proxy should be set according to the authentication scheme used, so for example, using AuthenticationSchemes.Ntlm would mean setting the UserName and Password properties on ChannelFactory.ClientCredentials.Windows.ClientCredential or perhaps ChannelFactory.ClientCredentials.HttpDigest.ClientCredential
With the second approach, be sure to note the difference between holding credentials in the ChannelFactory for use with the remote service versus credentials used for the proxy server. I've highlighted these in the code example below for clarity:
// Example service call using a CustomBinding that is configured for client
// authentication based on a user name and password sent as part of the message.
var binding = new CustomBinding();
TransportSecurityBindingElement securityBindingElement = SecurityBindingElement.CreateUserNameOverTransportBindingElement();
var secureTransport = new HttpsTransportBindingElement();
secureTransport.UseDefaultWebProxy = false;
secureTransport.ProxyAddress = new Uri("http://some-proxy");
secureTransport.ProxyAuthenticationScheme = AuthenticationSchemes.Ntlm;
binding.Elements.Add(securityBindingElement);
binding.Elements.Add(secureTransport);
var endpointAddress = new EndpointAddress("https://some-service");
var factory = new ChannelFactory<IService>(binding, endpointAddress);
// Credentials for authentication against the remote service
factory.Credentials.UserName.UserName = "serviceUser";
factory.Credentials.UserName.Password = "abc";
// Credentials for authentication against the proxy server
factory.Credentials.Windows.ClientCredential.UserName = "domain\user";
factory.Credentials.Windows.ClientCredential.Password = "xyz";
var client = factory.CreateChannel();
client.CallMethod();
I'm trying to execute an SSRS report in .NET Core.
Since .NET Core doesn't let you add service references, you have to use the WCF Connected Service to add a reference to the WSDL so it can generate .NET Core compatible code. This is what I did for ReportExecution2005.asmx (SQL Server 2016 if it matters).
I tried using the following to authenticate against the service:
var rsExec = new ReportExecutionServiceSoapClient(ReportExecutionServiceSoapClient.EndpointConfiguration.ReportExecutionServiceSoap,
new EndpointAddress("http://server/ReportServer/ReportExecution2005.asmx"))
{
ClientCredentials =
{
Windows =
{
AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation,
ClientCredential = new NetworkCredential("username", "password")
}
}
};
Also tried setting the Username object instead of Windows object, but either way the result is the following error:
MessageSecurityException: The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'NTLM'.
Looking at Fiddler, the code isn't passing the credentials along.
This is the code that got generated off the WSDL
public ReportExecutionServiceSoapClient(EndpointConfiguration endpointConfiguration, System.ServiceModel.EndpointAddress remoteAddress)
: base(ReportExecutionServiceSoapClient.GetBindingForEndpoint(endpointConfiguration), remoteAddress)
{
this.Endpoint.Name = endpointConfiguration.ToString();
ConfigureEndpoint(this.Endpoint, this.ClientCredentials);
}
static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials);
I may be mistaken, but isn't this calling the private method ConfigureEndpoint with the ClientCredentials object before the ClientCredentials object has even been set?
I'm not seeing any other way to configure the ClientCredentials or call ConfigureEndpoint, so how exactly are you supposed to authenticate? The other constructors are basically the same thing, except for one which takes in a Binding instead of an EndpointConfiguration. Any ideas?
After fighting with this for a day, I found an approach that seems to work, by using the only constructor that does not immediately call ConfigureEndpoint as pointed out in the question. If I create a binding that specifies NTLM, and I pass that binding along with a manually created endpoint, it works:
var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly)
{
Security =
{
Transport = new HttpTransportSecurity {ClientCredentialType = HttpClientCredentialType.Ntlm}
}
};
var reportService = new CssbiReportService.ReportExecutionServiceSoapClient(binding,
new EndpointAddress("http://myserver/ReportServer/ReportExecution2005.asmx"));
This is working for me in .NET Core.
Edit: update the code for .NET Core
Unfortunately, I don't have SSRS here to test the code right now.
But, try this code (no error check):
// parameters of report (if any)
ParameterValue[] parameters = {new ParameterValue {Name = "ApontamentoID", Value = "364"}};
// connect to the service
ReportExecutionServiceSoapClient webServiceProxy =
new ReportExecutionServiceSoapClient(
ReportExecutionServiceSoapClient.EndpointConfiguration.ReportExecutionServiceSoap,
"http://report_server_url/ReportExecution2005.asmx?wsdl");
// logon the user
await webServiceProxy.LogonUserAsync("username", "password", null);
// ask for the report
await webServiceProxy.LoadReportAsync("/report_path", null);
await webServiceProxy.SetExecutionParametersAsync(parameters, null);
// you can use RenderStreamRequest too
RenderRequest request = new RenderRequest("pdf", null);
RenderResponse response = await webServiceProxy.RenderAsync(request);
// save to the disk
System.IO.File.WriteAllBytes(#"c:\temp\output.pdf", response.Result);
// logoff the user
await webServiceProxy.LogoffAsync();
// close
await webServiceProxy.CloseAsync();
var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly)
{
Security =
{
Transport = new HttpTransportSecurity {
ClientCredentialType = HttpClientCredentialType.Ntlm
}
}
};
yourClient = ReportExecutionServiceSoapClient(rsBinding, rsEndpointAddress) {
ClientCredentials =
{ ...
^^^ This for NTLM.
Also, I was getting read-only errors trying to set some properties on the client after it had been created. In case it helps someone, properties must all be set at client-creation time to avoid this as per "yourClient" above.
I had the same problem, for me the following addition was helpful:
ReportExecutionServiceSoapClient rsClient = new ReportExecutionServiceSoapClient(rsBinding, rsEndpointAddress);
rsClient.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
I'm completely stuck with ONVIF authentication. I think I've tried everything or at least almost everything and I don't find enough information on the Internet. I have created the stub client using svcutil, my code to do the authentication is (one of them because I have tried a lot of things):
string uri = "http://140.0.22.39/onvif/services";
EndpointAddress serviceAddressPrueba = new EndpointAddress(uri);
HttpTransportBindingElement httpBinding = new HttpTransportBindingElement();
httpBinding.AuthenticationScheme = AuthenticationSchemes.Digest;
var messegeElement = new TextMessageEncodingBindingElement();
messegeElement.MessageVersion = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.None);
CustomBinding bindprueba = new CustomBinding(messegeElement, httpBinding);
DeviceClient clientprueba = new DeviceClient(bindprueba, serviceAddressPrueba);
string passwordDigestBase64;
//HERE I PUT THE CODE TO ENCRYPT THE PASSWORD.
PasswordDigestBehavior behavior1 = new PasswordDigestBehavior("root",passwordDigestBase64);
clientprueba.Endpoint.Behaviors.Add(behavior1);
string d1;
string d2;
string d3;
string d4;
clientprueba.GetDeviceInformation(out d1, out d2, out d3, out d4);
After this there is the following error:
{"The remote server returned an unexpected response: (400) Bad Request."}
I will be very, very grateful if you please could help me with any information to solve this.
Try this way:
ServicePointManager.Expect100Continue = false;
var endPointAddress = new EndpointAddress("http://" + cameraAddress + "/onvif/device_service");
var httpTransportBinding = new HttpTransportBindingElement { AuthenticationScheme = AuthenticationSchemes.Digest };
var textMessageEncodingBinding = new TextMessageEncodingBindingElement { MessageVersion = MessageVersion.CreateVersion(EnvelopeVersion.Soap12, AddressingVersion.None) };
var customBinding = new CustomBinding(textMessageEncodingBinding, httpTransportBinding);
var passwordDigestBehavior = new PasswordDigestBehavior(adminName, adminPassword);
var deviceClient = new DeviceClient(customBinding, endPointAddress);
deviceClient.Endpoint.Behaviors.Add(passwordDigestBehavior);
Notice that it is important to set ServicePointManager.Expect100Continue to false.
A couple of things could cause this:
You've set a root password via web browser, thus locking the ONVIF user. Log in to the camera and add an ONVIF user (There's a special page for that)
Your password digest includes only the password, where it should include a concatenation of a random nonce, the creation time, and the password.
Your local clock is not synchronized with the camera's clock. call getSystemDateAndTime to read the remote clock and record the time differences between you.
These were the 3 out of the 4 major things that slowed me down (the 4th one was importing the wsdl, but it looks like you got it already)
I'm asking the same question here that I've already asked on msdn forums http://social.msdn.microsoft.com/Forums/en-US/netfxnetcom/thread/70f40a4c-8399-4629-9bfc-146524334daf
I'm consuming a (most likely Java based) Web Service with I have absolutely no access to modify. It won't be modified even though I would ask them (it's a nation wide system).
I've written the client with WCF. Here's some code:
CustomBinding binding = new CustomBinding();
AsymmetricSecurityBindingElement element = SecurityBindingElement.CreateMutualCertificateDuplexBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
element.AllowSerializedSigningTokenOnReply = true;
element.SetKeyDerivation(false);
element.IncludeTimestamp = true;
element.KeyEntropyMode = SecurityKeyEntropyMode.ClientEntropy;
element.MessageProtectionOrder = System.ServiceModel.Security.MessageProtectionOrder.SignBeforeEncrypt;
element.LocalClientSettings.IdentityVerifier = new CustomIdentityVerifier();
element.SecurityHeaderLayout = SecurityHeaderLayout.Lax;
element.IncludeTimestamp = false;
binding.Elements.Add(element);
binding.Elements.Add(new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8));
binding.Elements.Add(new HttpsTransportBindingElement());
EndpointAddress address = new EndpointAddress(new Uri("url"));
ChannelFactory<MyPortTypeChannel> factory = new ChannelFactory<MyPortTypeChannel>(binding, address);
ClientCredentials credentials = factory.Endpoint.Behaviors.Find<ClientCredentials>();
credentials.ClientCertificate.Certificate = myClientCert;
credentials.ServiceCertificate.DefaultCertificate = myServiceCert;
credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
service = factory.CreateChannel();
After this every request done to the service fails in client side (I can confirm my request is accepted by the service and a sane response is being returned)
I always get the following exception
MessageSecurityException: The security
header element 'Timestamp' with the ''
id must be signed.
By looking at trace I can see that in the response there really is a timestamp element, but in the security section there is only a signature for body.
Can I somehow make WCF to ingore the fact Timestamp isn't signed?
You could try using a WCF Message contract. When you have a Message contract you can specify that the items in the header should be signed:
[MessageContract]
public class CustomType
{
[MessageHeader(ProtectionLevel = ProtectionLevel.Sign)]
string name;
[MessageHeader(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
string secret;
I got this sorted out, the answer can be found in here:
http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/371184de-5c05-4c70-8899-13536d8f5d16
Main points: add a custom StrippingChannel to the custombinding to strip off the timestamp from the securityheader and configure WCF to not detect replies.