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();
Related
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);
}
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 am trying to call a web service on my server over https from my mvc3 app. I have web services located at this address:
https:localhost/web_services/web_services.asmx
And in my code i try to connect like this:
var binding = new BasicHttpsBinding();
binding.maxbuffersize = 10000;
binding.maxbufferPoolsize = 10000;
binding.maxreceivedmessageSize= 10000;
binding.Security.Mode = System.ServiceModel.BasicHttpsSecurityMode.Transport;
binding.Security.Transport.ClientCredentialsType = HttpClientCredentialType.Certificate
var endpointAddress = new EndpointAddress("https:/localhost/web_services/web_services.asmx");
new ChannelFactory<ws_name_webreqSoap>(basicHttpsBinding, endpointAddress).CreateChannel();
var webServices = new ws_name_webreqSoapClient(basicHttpsBinding, endpointAddress);
However, when this runs on the server, i get the following message:
"The client certificate is Not provided. Specify a client certificate in client credentials"
My knowledge of HTTPS and certificates is limited. Does anyone know a solution to this?
Thanks,
You can specify the client certificate on the ChannelFactory:
var channelFactory = new ChannelFactory<ws_name_webreqSoap>(basicHttpsBinding, endpointAddress);
channelFactory.Credentials.ClientCertificate.SetCertificate("CN=client.com", StoreLocation.CurrentUser, StoreName.My);
var channel = channelFactory.CreateChannel();
// ...
I am trying to consume a self-hosted WCF application using SSL and a custom authentication validator from within an integration test. So far I am able to self-host the service but I am not able to figure out how to consume it.
Here is the self-hosting code (it is not dependent on Web.Config, as far as I know):
[ClassInitialize]
public static void TestClassInitialize(TestContext testContext)
{
const string serviceAddress = "https://localhost/SelfHostedService";
Uri _svcEndpointUri = new Uri(serviceAddress);
var binding = new WSHttpBinding
{
Security =
{
Mode = SecurityMode.TransportWithMessageCredential,
Message = {ClientCredentialType = MessageCredentialType.UserName}
}
};
ServiceDebugBehavior debugBehavior = new ServiceDebugBehavior
{
IncludeExceptionDetailInFaults = true
};
MyServiceApi _api = new MyServiceApi();
ServiceHost _svcHost = new ServiceHost(_api, _svcEndpointUri);
_svcHost.Description.Behaviors.Remove<ServiceDebugBehavior>();
_svcHost.Description.Behaviors.Add(debugBehavior);
// Ensure that SSL certificate & authentication interceptor get used
ServiceCredentials credentials = new ServiceCredentials();
credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new MyCustomAuthenticationValidator();
credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "SubjectName");
_svcHost.Description.Behaviors.Remove<ServiceCredentials>();
_svcHost.Description.Behaviors.Add(credentials);
// Add IUbiquity and mex endpoints
Uri endpointAddress = new Uri(serviceAddress + "/UbiquityApi.svc");
_svcHost.AddServiceEndpoint(typeof (IUbiquityApi), binding, endpointAddress);
// Specify InstanceContextMode, which is required to self-host
var behavior = _svcHost.Description.Behaviors.Find<ServiceBehaviorAttribute>();
behavior.InstanceContextMode = InstanceContextMode.Single;
_svcHost.Open();
}
What I'd like to be able to do looks like this, but I have no idea how I'd go about accomplish this:
[TestMethod]
public void TestAuthentication(){
var api = _svcHost.MagicallyRetrieveServiceInstance();
api.Credentials = new MagicCredentials("my username", "my password");
Assert.AreEqual(3, api.AddNumbers(1,2));
// Also assert that I am authenticated
api.Credentials = new MagicCredentials("my username", "my password");
bool exceptionWasThrown = false;
try {
api.AddNumbers(1,2);
}
catch(NotLoggedInException l){ // or something
exceptionWasThrown = true;
}
Assert.IsTrue(exceptionWasThrown);
}
My ideal solution would allow me to retrieve the service contract from the service host, and allow me to set the credentials used for the service contract. I should only have to supply the credentials once to the service contract, and then I should be able to call methods directly, as if I were communicating over the wire (thus making this an integration test). How should I go about this?
To consume the web service, simply add the service as a service reference, and then use the service reference client.
Done right, this will take care of the bindings needed for authentication, effectively putting the WCF configurations under test.
Is there any example of using a WCF REST service with basic HTTP authentication from a desktop client?
I am using WCF REST Contrib. and authentication works fine when a use a javascript client from the browser, but when I try to use a C# Console app. I get a BasicUnauthorizedException {"You have unsuccessfully attempted to access a secure resource."}. even though I supplied the correct username and password.
WebHttpBinding binding = new WebHttpBinding();
binding.SendTimeout = TimeSpan.FromSeconds(25);
binding.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
Uri address = new Uri("http://localhost:3525/wcfrestdemo/students.svc");
WebChannelFactory<ISudentService> factory =
new WebChannelFactory<ISudentService>(binding, address);
factory.Credentials.UserName.UserName = "jon";
factory.Credentials.UserName.Password = "123";
ISudentService proxy = factory.CreateChannel();
var response = proxy.GetStudents(2010, 4, 2); //throws an error.
Any help will be appreciated.