Configuring WCF client binding to use X509 certificate in dotnet core 2.2 - wcf

I'm trying to convert an old WCF client to dotnet core. I successfully generated my proxies from the wsdl and have been trying to configure them so I can successfully call the endpoint. It appears, based on some googling, that under dotnet core I need to configure my WCF client from code.
Here's the WCF configuration section from the web.config of the old application:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<behaviors>
<endpointBehaviors>
<behavior name="clientEndpointCredential">
<clientCredentials>
<clientCertificate storeName="My" storeLocation="LocalMachine" x509FindType="FindBySubjectName" findValue="CERTNAME" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="OUR_Customer_OUTBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="2147483647" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="5242880" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="Transport">
<transport clientCredentialType="Certificate" proxyCredentialType="None" realm="" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://the-full-url" behaviorConfiguration="clientEndpointCredential" binding="basicHttpBinding" bindingConfiguration="OUR_Customer_OUTBinding" contract="CustomerInterface.OUR_Customer_OUT" name="HTTPS_Port" />
</client>
<diagnostics>
<messageLogging logEntireMessage="true" logMalformedMessages="true" logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="false" maxMessagesToLog="3000" />
</diagnostics>
</system.serviceModel>
Here's what I've come up with to configure it in dotnet core:
private OUR_Customer_OUTClient GetCustomerClient()
{
TimeSpan Minutes(int minutes) => new TimeSpan(0, minutes, 0);
var binding = new BasicHttpBinding();
binding.Name = "OUR_Customer_OUTBinding";
binding.AllowCookies = false;
binding.SendTimeout = Minutes(1);
binding.ReceiveTimeout = Minutes(10);
binding.OpenTimeout = Minutes(1);
binding.CloseTimeout = Minutes(1);
binding.MaxBufferPoolSize = 2147483647;
binding.MaxReceivedMessageSize = 2147483647;
binding.TextEncoding = Encoding.UTF8;
binding.TransferMode = TransferMode.Buffered;
binding.BypassProxyOnLocal = false;
binding.UseDefaultWebProxy = true;
binding.Security.Mode = BasicHttpSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
binding.Security.Transport.ProxyCredentialType = HttpProxyCredentialType.None;
var endpointAddress = new EndpointAddress("https://the-full-url");
var client = new OUR_Customer_OUTClient(binding, endpointAddress);
client.ClientCredentials.ClientCertificate.SetCertificate(
StoreLocation.LocalMachine,
StoreName.My,
X509FindType.FindBySubjectName,
"CERTNAME");
return client;
}
And here's the code I'm using to call the endpoint (dotnet core proxies don't yet support synchronous calls):
SearchResponse searchResponse = Task.Run(() => GetCustomerClient().SearchAsync(message)).Result;
However, I'm getting the following error:
The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Basic realm="XISOAPApps"'
Can anyone see anything wrong with my approach or suggest ways I could use to debug this? I'm a WCF newbie and am tearing my hair out at this point.

For the benefit of any others who may be unlucky enough to hit the same problem, the central issue turned out to be that the X509 certificate was not being sent. (The endpoint we were hitting accepted either a certificate or basic auth, thus the 401.)
The reason the certificate wasn't being sent was because the dotnet core networking stack is stricter than the .NET one, and requires the certificate to either have its Enhanced Key Usage set to ClientAuthentication (1.3.6.1.5.5.7.3.2) or have no EKU at all (see the source code here). Ours wasn't - it was set to Server Authentication. So the certificate was quietly discarded, despite having been loaded up successfully.
This github issue provides further details.

Your code snippets seem good. We may have one more thing to do. when server authenticates the client with a certificate, we should establish the trust relationship each other, please refer to the below link.
https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/transport-security-with-certificate-authentication
Besides, we should provider an Identity flag to identity the server, like below.
<client>
<endpoint address="http://vabqia593vm:4434/Service1.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IService1" contract="ServiceReference1.IService1" name="WSHttpBinding_IService1" behaviorConfiguration="mybeh">
<identity>
<dns value="vabqia130vm"/>
</identity>
</endpoint>
</client>
We could generate client proxy class by Micorosoft WCF Web Service Reference Provider.(Add Connected Services).
Feel free to let me know if there is anything I can help with.
Abraham

Related

Problem increasing WCF service endpoint size configuration

I have an asp.net project that was hosting a WCF service (via the wcf file exposure technique for IIS) that was put together around 10 years ago. There is an operation on the service that retrieves content that is greater than the default 75K limit. The service configuration (in web.config) property on the binding was adjusted to be larger than the default and was working ok. The ASP.NET project was upgraded in VS2017/2019 - and (I assume) the project was adjusted (somehow). The service operation described above no longer works. I did a lot of investigation and all solutions were handled (which makes sense as the original version of the project was working). I even went through the process of building a new web.config with minimal configuration and still did not work. I also adjusted the service to include the Configure operation that could be used to configure the endpoint configuration in code. It also did not work. For this I have the following:
public static void Configure(ServiceConfiguration config)
{
BasicHttpBinding binding = new BasicHttpBinding();
binding.MaxReceivedMessageSize = int.MaxValue;
binding.MaxBufferSize = int.MaxValue;
binding.MaxBufferPoolSize = int.MaxValue;
binding.BypassProxyOnLocal = true;
binding.ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas()
{
MaxStringContentLength = int.MaxValue,
MaxDepth = int.MaxValue,
MaxArrayLength = int.MaxValue,
MaxBytesPerRead = int.MaxValue,
MaxNameTableCharCount = int.MaxValue
};
var endpoint = new ServiceEndpoint(
ContractDescription.GetContract(
typeof(IAuthenticateServicePack)),binding,new
EndpointAddress("http://localhost:49437/WCFSevices/AuthenticateServicePack.svc"));
//MyEndpointBehavior Behavior = new MyEndpointBehavior();
//endpoint.Behaviors.Add(Behavior);
config.AddServiceEndpoint(endpoint);
}
The web.config definitions is the following
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true"
/>
<behaviors>
<serviceBehaviors>
<behavior name="ServicePack.ServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceThrottling maxConcurrentCalls="500" maxConcurrentSessions="500"
/>
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<clear />
<binding name="myBindingForBigArrays" maxReceivedMessageSize="2147483647"
transferMode="Buffered">
<readerQuotas
maxDepth="32"
maxStringContentLength="2147483647"
maxArrayLength="2147483647"
maxBytesPerRead="4096"
maxNameTableCharCount="10000"
/>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="Lapp.WebAdmin.Mvc.WCFSevices.AuthenticateServicePack"
behaviorConfiguration="ServicePack.ServiceBehavior">
<endpoint address="" binding="basicHttpBinding"
bindingConfiguration="myBindingForBigArrays"
contract="Lapp.WebAdmin.Mvc.WCFSevices.IAuthenticateServicePack" />
<host>
<timeouts closeTimeout="00:01:00" />
</host>
</service>
</services>
</system.serviceModel>
For some reason - the updated asp.net project does not seem to be handling the WCF service endpoint configuration. Has anyone run into this issue?
Note: I can test the older version of the service and is working properly -- and reviewing the old config with the new -- cannot identify any modification that would appear to be causing the new not to use the size limit settings.

Trouble connecting to API

I am new to WCF/APIs and know little to nothing about security. Let me know if I need to provide any more information.
I am trying to connect to a service using
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_ISalesOrderService">
<security mode="Transport" >
<transport clientCredentialType="Basic"></transport>
</security>
</binding>
<binding name="BasicHttpBinding_IDocumentationService">
<security mode="TransportCredentialOnly" >
<transport clientCredentialType="Basic"></transport>
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="address1"
name="BasicHttpBinding_ISalesOrderService"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_ISalesOrderService"
contract="SoCalls.ISalesOrderService" />
<endpoint address="address2"
binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IDocumentationService"
contract="DocCalls.IDocumentationService"
name="BasicHttpBinding_IDocumentationService" />
</client>
</system.serviceModel>
With this, I get this error:
'System.ServiceModel.Security.MessageSecurityException'
The HTTP request is unauthorized with client authentication scheme 'Basic'.
The authentication header received from the server was 'Basic Realm'.
Edit
I followed the instructions suggested in the link provided in the comments, still is giving me this error. I updated my code but I think I am still a bit confused on whether to use HTTP/HTTPS due to lack of knowledge of either service.
Here is how I instantiate my service:
private static SoCalls.SalesOrderServiceClient CreateSalesOrderServiceClient()
{
BasicHttpBinding myBinding = new BasicHttpBinding();
myBinding.MaxReceivedMessageSize = 10000 * 2;
myBinding.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
myBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
EndpointAddress ea = new EndpointAddress("address1");
SoCalls.SalesOrderServiceClient client = new SoCalls.SalesOrderServiceClient();
client.ClientCredentials.UserName.UserName = ("username");
client.ClientCredentials.UserName.Password = ("password");
return client;
}
As it seems, I did not have access to the API with this specific database which is what was giving me my error. I tried sending other credentials for a separate database from the same server and it worked perfectly fine. Purchased the license required and the code works as expected.

WCF ERROR: The server did not provide a meaningful reply;

please somebody can help me to find out what is happened. I have my WCF service which worked fine, and now suddenly I have this error:
The server did not provide a meaningful reply; this might be caused by a contract mismatch, a premature session shutdown or an internal
server error
I must tell that it still works when I select some thousands of records, but when the data is huge I receive this error, although before it worked fine!
private static string ConnString = "Server=127.0.0.1; Port=5432; Database=DBname; User Id=UName; Password=MyPassword;"
DataTable myDT = new DataTable();
NpgsqlConnection myAccessConn = new NpgsqlConnection(ConnString);
myAccessConn.Open();
string query = "SELECT * FROM Twitter";
NpgsqlDataAdapter myDataAdapter = new NpgsqlDataAdapter(query, myAccessConn);
myDataAdapter.Fill(myDT);
foreach (DataRow dr in myDT.Rows)
{
**WHEN I HAVE TOO MANY RECORDS IT STOPS HERE**
...
web.config
<configuration>
<system.web>
<compilation debug="false" targetFramework="4.0" />
<httpRuntime maxRequestLength="2147483647" executionTimeout="100000" />
</system.web>
<system.diagnostics>
<trace autoflush="true" />
<sources>
<source name="System.ServiceModel"
switchValue="Information, ActivityTracing"
propagateActivity="true">
<listeners>
<add name="traceListener" type="System.Diagnostics.XmlWriterTraceListener" initializeData="Traces4.svclog"/>
</listeners>
</source>
</sources>
</system.diagnostics>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IDBService" closeTimeout="00:30:00"
openTimeout="00:30:00" receiveTimeout="00:30:00" sendTimeout="00:30:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="2147483647" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647"
messageEncoding="Text" textEncoding="utf-8" transferMode="Streamed"
useDefaultWebProxy="true">
<readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647"
maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IDBService" contract="DBServiceReference.IDBService"
name="BasicHttpBinding_IDBService" />
</client>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<dataContractSerializer maxItemsInObjectGraph="2147483646" />
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="false" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
</configuration>
client config (Edited)
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IRouteService" maxBufferSize="2147483647"
maxReceivedMessageSize="2147483647">
<security mode="None" />
</binding>
<binding name="BasicHttpBinding_IDBService" closeTimeout="00:30:00"
openTimeout="00:30:00" receiveTimeout="00:30:00" sendTimeout="00:30:00"
maxBufferSize="2147483647" maxReceivedMessageSize="2147483647"
transferMode="Buffered" >
<security mode="None" />
</binding>
</basicHttpBinding>
<customBinding>
<binding name="CustomBinding_IRouteService">
<binaryMessageEncoding />
<httpTransport maxReceivedMessageSize="2147483647"
maxBufferSize="2147483647" />
</binding>
</customBinding>
</bindings>
<client>
<endpoint address="http://dev.virtualearth.net/webservices/v1/routeservice/routeservice.svc"
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IRouteService"
contract="BingRoutingService.IRouteService" name="BasicHttpBinding_IRouteService" />
<endpoint address="http://dev.virtualearth.net/webservices/v1/routeservice/routeservice.svc/binaryHttp"
binding="customBinding" bindingConfiguration="CustomBinding_IRouteService"
contract="BingRoutingService.IRouteService" name="CustomBinding_IRouteService" />
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IDBService"
contract="DBServiceReference.IDBService" name="BasicHttpBinding_IDBService" />
</client>
</system.serviceModel>
</configuration>
In my file scvlog I don' t get any exception!
I don't have any other idea what else I can do for understand where is the problem. Please somebody help me!!!
A different answer, just in case anyone arrives here as I did looking for a general answer to the question.
It seems that the DataContractSerializer that does the donkey-work is incredibly finicky, but doesn't always pass the real error to the client. The server process dies straight after the failure - hence no error can be found. In my case the problem was an enum that was used as flags, but not decorated with the [Flags] attribute (picky or what!).
To solve it I created an instance of the serializer and inspected the error in the debugger; here's a code snippet since I have it to hand.
EDIT: In response to request in comments ...
Amended the code snippet to show the helper method I now use. Much the same as before, but in a handy generic wrapper.
public static T CheckCanSerialize<T>(this T returnValue) {
var lDCS = new System.Runtime.Serialization.DataContractSerializer(typeof(T));
Byte[] lBytes;
using (var lMem1 = new IO.MemoryStream()) {
lDCS.WriteObject(lMem1, returnValue);
lBytes = lMem1.ToArray();
}
T lResult;
using (var lMem2 = new IO.MemoryStream(lBytes)) {
lResult = (T)lDCS.ReadObject(lMem2);
}
return lResult;
}
And to use this, instead of returning an object, return the object after calling the helper method, so
public MyDodgyObject MyService() {
... do lots of work ...
return myResult;
}
becomes
public MyDodgyObject MyService() {
... do lots of work ...
return CheckCanSerialize(myResult);
}
Any errors in serialization are then thrown before the service stops paying attention, and so can be analysed in the debugger.
Note; I wouldn't recommend leaving the call in production code, it has the overhead of serializing and deserializing the object, without any real benefit once the code is debugged.
Hope this helps someone - I've wasted about 3 hours trying to track it down.
I don't know if it's really can be an answer, but I have tried to change in web.config from <security mode="None" /> to <security mode="Transport" /> and It worked!!!
I'd want to pay attention that this part should be changed only in web.config and in client configuration remains <security mode="None" />, because with Transport in both It doesn't work!
So after that, I decided to try to come back again to None security and It worked for some minutes and then stopped again, and it came back the error:
The server did not provide a meaningful reply; this might be caused by a contract mismatch, a premature session shutdown or an internal server error
So It seems that the solution in my case is to set in web.config
security mode to Transport
In my case, I was working on a windows app project communicating with a WCF Web Service.
The web service, using netTcpBinding was returning a Stream object (a picture).
As the windows app doesn't have configuration file, default values are used for bindings. And simply extending the MaxReceivedMessageSize on the client side backend code solved my problem.
var API = new StreamService.StreamServiceClient(
new System.ServiceModel.NetTcpBinding(System.ServiceModel.SecurityMode.None)
{
MaxReceivedMessageSize = 2147483647
},
new System.ServiceModel.EndpointAddress("net.tcp://machine/app/service.svc")
);
Sometimes this problem is caused by an oversized message that was cut due to default values in the binding.
You should add maxReceivedMessageSize, maxBufferPoolSize and maxBufferSize with some large enough values to the binding in your app.config file - that should do the trick :)
Example:
<bindings>
<netTcpBinding>
<binding
name="ExampleBinding" closeTimeout="00:01:00"
maxReceivedMessageSize="73400320"
maxBufferPoolSize="70000000"
maxBufferSize="70000000"/>
</netTcpBinding>
</bindings>
Good Luck!
In my case I was working on an MVC application and I have changed
maxReceivedMessageSize ="10000000"
to
maxReceivedMessageSize ="70000000"
and it worked! It's because the response from the web server exceeds maxReceivedMessageSize ="10000000",
so I have increased maxReceivedMessageSize to maxReceivedMessageSize ="70000000".
In my experience of this error, just check the service's host computer's event log to see what is the actual root exception.
For me it was a lazy-loading list of items retrieved from the DB.
The WCF receiver would try to iterate them, which would try to go to the DB, which obviously could not work.
In BizTalk we use to get this issue.
Mostly the issue will happen due to size of the message from the service. So we need to increase the size of the receiving message from 65,356 to 2,365,60. It worked for me.
enter image description here
ASP.NET applications can execute with the Windows identity (user account) of the user making the request. Impersonation is commonly used in applications that rely on Microsoft Internet Information Services (IIS) to authenticate the user. ASP.NET impersonation is disabled by default.
Enable this, your API will start working - it is in IIS authentication
In my case, after upgrading from .NET Framework 4.5 to .NET Framework 4.8, I had to remove read-only modifiers of properties that were decorated with DataMemberAttribute.

In WCF, for a webHttpBinding, how do I specify credentials in the client side web.config when the server is using basic authentication?

I have two WCF RESTful services - the "general" service is public and has no security; the "admin" service I intend to use basic authentication over SSL. This is my server side web.config:
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="general" maxReceivedMessageSize="2147483647">
<readerQuotas maxArrayLength="2147483647" maxStringContentLength="2147483647" />
<security mode="None">
<transport clientCredentialType="None" />
</security>
</binding>
<binding name="admin" maxReceivedMessageSize="2147483647">
<readerQuotas maxArrayLength="2147483647" maxStringContentLength="2147483647" />
<security mode="Transport">
<transport clientCredentialType="Basic" />
</security>
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="web">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<services>
<service name="MyNamespace.AppServices.GeneralService">
<endpoint address="" binding="webHttpBinding" contract="MyNamespace.Contracts.IGeneralService" behaviorConfiguration="web" bindingConfiguration="general" />
</service>
<service name="MyNamespace.AppServices.AdminService">
<endpoint address="" binding="webHttpBinding" contract="MyNamespace.Contracts.IAdminService" behaviorConfiguration="web" bindingConfiguration="admin" />
</service>
</services>
</system.serviceModel>
On the client side, I currently have code that looks like this:
private static IGeneralService GetGeneralChannel()
{
WebHttpBinding binding = new WebHttpBinding();
binding.Security.Mode = WebHttpSecurityMode.None;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;
binding.MaxReceivedMessageSize = Int32.MaxValue;
binding.ReaderQuotas.MaxStringContentLength = Int32.MaxValue;
binding.ReaderQuotas.MaxArrayLength = Int32.MaxValue;
WebChannelFactory<IGeneralService> cf = new WebChannelFactory<IGeneralService>(binding, new Uri("http://localhost:1066/GeneralService"));
IGeneralService channel = cf.CreateChannel();
return channel;
}
private static IAdminService GetAdminChannel()
{
WebHttpBinding binding = new WebHttpBinding();
binding.Security.Mode = WebHttpSecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
binding.MaxReceivedMessageSize = Int32.MaxValue;
binding.ReaderQuotas.MaxStringContentLength = Int32.MaxValue;
binding.ReaderQuotas.MaxArrayLength = Int32.MaxValue;
WebChannelFactory<IAdminService> cf = new WebChannelFactory<IAdminService>(binding, new Uri("http://localhost:1066/AdminService"));
cf.Credentials.UserName.UserName = "myUserName";
cf.Credentials.UserName.Password = "myPassword";
IAdminService channel = cf.CreateChannel();
return channel;
}
The question is, since I obviously do not want to hard-code all of this configuration information, how do I need to provide it in the web.config on the client? It is pretty clear to me that the binding element needs to look pretty much the same on the client as it does on the server. However, where do I indicate the credentials that are assigned to the WebChannelFactory?
Any help and/or insight will be appreciated.
Thanks,
Steve
You cannot put those credentials (username and password) into web.config and have WCF read them from there. This is one of the very few features in WCF which cannot be done in config - you have to set those credentials in your code.
Of course, in your code, you can read them from e.g. a database table, or a config entry somewhere - but you have to do that yourself. WCF can't be configured to automagically read those settings from somewhere.

The HTTP request is unauthorized with client authentication scheme 'Basic'. The authentication header received from the server was 'Basic realm="pc"'

Server:
<system.serviceModel>
<services>
<service name="Service" behaviorConfiguration="md">
<!-- Service Endpoints -->
<endpoint address="SslService" binding="basicHttpBinding" bindingConfiguration="security" contract="IService"/>
<host>
<baseAddresses>
<add baseAddress="https://pc:8080/Service.svc"/>
</baseAddresses>
</host>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name="security">
<security mode="Transport">
<transport clientCredentialType="Basic"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="md">
<serviceCredentials>
<userNameAuthentication
userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="ClassLibrary1.CustomUserNameValidator, ClassLibrary1" />
</serviceCredentials>
<serviceMetadata httpsGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
ClassLibrary1.CustomUserNameValidato:
public class CustomUserNameValidator : System.IdentityModel.Selectors.UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (userName != "111" || password != "111")
{
throw new System.ServiceModel.FaultException("Unknown username or incorrect password");
}
}
}
Client:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="Transport">
<transport clientCredentialType="Basic" proxyCredentialType="Basic" realm="">
<extendedProtectionPolicy policyEnforcement="Never" />
</transport>
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://pc:8080/Service.svc/SslService" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IService" contract="ServiceReference1.IService"
name="BasicHttpBinding_IService" />
</client>
</system.serviceModel>
ServiceReference1.ServiceClient s = new WindowsFormsApplication1.ServiceReference1.ServiceClient();
s.ClientCredentials.UserName.UserName = "111";
s.ClientCredentials.UserName.UserName = "111";
MessageBox.Show(s.GetData(3)); // <---- ERROR
The HTTP request is unauthorized with client authentication scheme 'Basic'. The authentication header received from the server was 'Basic realm="pc"'.
I had created a client like this:
using (var client = new Client())
{
client.ClientCredentials.UserName.UserName = <username>;
client.ClientCredentials.UserName.Password = **<WRONG_PASSWORD>**;
...
}
The security section of my binding looked like this:
<security mode="Transport">
<transport clientCredentialType="Basic" proxyCredentialType="Basic" realm="" />
</security>
And I saw this error come back. Once I corrected the password, everything worked.
I'm assuming you are hosting your servicehost on the IIS. Then the problem is that the IIS intercepts the https request and performs IIS-level authentication before the WCF framework and your custom validator has a chance to kick in.
In your example, the IIS will actually look for a local user '111' with password '111' on the server running the IIS. Try creating this user on the server, and you will probably get a different result.
One solution is to host your WCF servicehost somewhere else, for example in a Windows Service. Another solution is to change your security scheme to TransportWithMessageCredential. Finally, you could check this OSS http module out: Custom Basic Authentication for IIS - seems to do the trick we need.
Try to send username and password not in http with basic authentication (this can embarrass IIS), but only in soap-message headers with following scheme:
<binding name="...">
<security mode="TransportWithMessageCredential" >
<message clientCredentialType="UserName" />
</security>
</binding>
How to: Use Transport Security and Message Credentials
Maybe you also need to additionally specify <transport clientCredentialType="None">
I posted an answer here: Can not call web service with basic authentication using WCF
transport clientcredentialType is TransportCredentialOnly
Looks like you set the user name twice instead of the user name and password.
When you have basic authentication and you do not send the username and password with the request you get a challenge response back.
I applied all the things which are above mentioned but my problem is not solved.
In my case this is happening because of proxy server. Then I removed all the proxy and run my web service. Then it is working fine.
Hope this will you!!!!!!