Intranet - web to remote wcf CredentialCache.DefaultNetworkCredentials not working - c#

I have a ASP.NET MVC intranet application hosted in IIS that added WCF service reference the WCF resides in another computer and also expect windows authentication.
In my web this code is working great:
proxy = new MyProxyClient("configurationName", "remoteAddress");
proxy.ClientCredentials.Windows.ClientCredential.UserName = "myUserName";
proxy.ClientCredentials.Windows.ClientCredential.Password = "MyPassword";
proxy.SomeMethod(); //work great
but if I want the credential not to be hardcoded like this I am using: CredentialCache.DefaultNetworkCredentials like this:
proxy = new MyProxyClient("configurationName", "remoteAddress");
proxy.ClientCredentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
proxy.SomeMethod(); //not working throw exception
the above code throw SecurityNegotiationException with message: The caller was not authenticated by the service.
and the inner exception is: The request for security token could not be satisfied because authentication failed.
How can I pass the credential of the current user to the WCF service without hardcoded user name and password?

If your organization uses regular Windows authentication (NTLM) you can't do what you want due to "one-hop" restriction: credentials passed from user's computer to your server use "one-hop" (from direct login to one external computer) and such credentials can't be used to authenticate other servers from the first one.
More information can be found using following search term:ntlm one hop,i.e. Why NTLM fails and Kerberos works.
Standard solution:
Kerberos (often requires significant effort to get approval to enable/configure)
Use some other form of authentication than Windows. Consider if OAuth is possible. Don't go basic auth.
Switch WCF service to claims based authentication.
If WCF service can it can trust caller to verify incoming credentials more approaches are possible:
Run code under particular account that signs in locally on server and have permissions to call the service. The easiest approach is what shown in your post, but storing domain passwords (or any passwords) in plain text is not secure. One can also run process account under special credentials that have access to the remote service and temporary revert impersonation after verifying user credentials.
You can also configure WCF service to require client certificate and use such certificate when calling the WCF service. This way WCF service can verify if caller is known.

In the web.config (client and server), in the <system.serviceModel> section add/modify a binding to look something like this:
<basicHttpBinding>
<binding name="MyBasicBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
And, add this to client side web.config <system.web> section:
<identity impersonate="true" />
<authentication mode="Windows" />
The two changes will make the end-user the current user of the web request which will then be sent in the WCF message.
The user can then be retrieved on the server side like this:
ServiceSecurityContext.Current.WindowsIdentity
Please make sure the following configuration is there in service web.config.
<system.serviceModel>
<services>
<service name="MyWcf.Service1" behaviorConfiguration="MySvcBehavior">
<endpoint address="" binding="wsHttpBinding" contract="MyWcf.IService1" bindingConfiguration="MyWsHttpBinding"></endpoint>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="MyWsHttpBinding">
<security mode="Message">
<transport clientCredentialType="Windows"/>
</security>
</binding>
</wsHttpBinding>
<basicHttpBinding>
<binding name="MyBasicBinding">
<security mode="Message">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="MySvcBehavior">
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
And in your client configuration file following should be there.
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IService1">
<security>
<transport clientCredentialType="Windows" />
<message clientCredentialType="Windows" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost/MyWcf/Service1.svc" binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IService1" contract="MyWCFService.IService1"
name="WSHttpBinding_IService1">
</endpoint>
</client>
</system.serviceModel>
I tried by passing System.Net.CredentialCache.DefaultNetworkCredentials using above configuration and working fine for me. If it's not working for you then put debug point on line which passing credential and watch that System.Net.CredentialCache.DefaultNetworkCredentials Domain, UserName & Password values are blank or not. If Blank then it should work.

I assume that if you're looking within your CredentialCache.DefaultNetworkCredentials attribut you won't see the credential information.
From Microsoft website : The authentication informations return by the DefaultNetworkCredentials property will be only available for NTLM, Negotiate or Kerberos authentication.
To get your credential you need to implement this authentication.
You can use it by using impersonation wihtin your intranet application.
Impersonation allow your intranet application to be executed by the user of this one.
More information here :http://technet.microsoft.com/en-us/library/cc961980.aspx

Related

Debug local WCF service with Basic Authentication always returns 401 - Unauthorized

I have a problem accessing a .Net WCF Service that uses Basic authentication. The server's web.config file has the service configured as such:
<services>
<service behaviorConfiguration="serviceBehavior" name="api.GlobalService">
<endpoint address="" behaviorConfiguration="restBehavior" binding="basicHttpBinding"
bindingConfiguration="Basic" contract="api.IGlobalService" />
</service>
</services>
with the binding:
<bindings>
<basicHttpBinding>
<binding name="Basic">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
In my IIS Express config file I enabled basic authentication as such:
<basicAuthentication enabled="true" />
I am running it in debug mode, on localhost, and I don't want a custom basic authentication, I want it to authenticate against Windows credentials. I access the server directly, from the browser, and enter my windows credentials when prompted, or from Postman using basic authentication and credentials, however I always get a 401. I am not authorized to access a server I run on my own machine with my own credentials. Any help on what I'm doing wrong?
You can try the following, in the application that consumes the WCF Service
WCFServiececlient.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
WCFServiececlient.ChannelFactory.Credentials.Windows.ClientCredential = System.Net.CredentialCache.DefaultNetworkCredentials;

httpBasicBinding with authentication and no message or transport security

I've got a small WCF service in intranet and I need to implement authentication in it. This service communicates with different Java clients over http (uses basicHttpBinding). I've tried to configure binding like so
<basicHttpBinding>
<binding>
<security mode="None">
<message clientCredentialType="UserName"/>
</security>
</binding>
</basicHttpBinding>
And added service behaviour
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="LocalTasks.Services.Validators.IntegrationUserNameValidator,LocalTasks.Services"/>
</serviceCredentials>
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
But calls to this service do not hit my custom UserNamePassword validator.
I've also tried the same variant but with security mode set to Message and added a certificate. In this case validator worked fine.
How to configure service to authenticate users via basicHttpBinding without any message or transport security?
You cannot do that. You need either message or transport security, in order to have the credentials encrypted when they travel from the client to the server.
You really should use Message or Transport security.
You could also use TransportCredentialOnly, which will not require an SSL certificate, but that is recommended for testing only.
<basicHttpBinding>
<binding name="Authentication" >
<security mode="TransportCredentialOnly" >
<message clientCredentialType="UserName"/>
</security>
</binding>
</basicHttpBinding>

Getting current user from a basic WCF service project

I have a basic WCF service project in Visual Studio 2010, which consists of a .svc file, the associated .cs, and Web.config. My problem is that when using the WCF Test Client I can't get a user. OperationContext.Current.ServiceSecurityContext is null, as is HttpContext.Current. I managed to get security set to Transport on the WCF client, but now I'm getting the following error:
The provided URI scheme 'http' is invalid; expected 'https'.
My Web.config is as follows:
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
<serviceMetadata httpGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
My WCF Test Client config is as follows:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_MyService">
<security mode="Transport">
<transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
<message clientCredentialType="Certificate" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:57165/MyService.svc" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_MyService" contract="MyService"
name="BasicHttpBinding_MyService" />
</client>
</system.serviceModel>
</configuration>
I'm totally new to writing new services, so the other posts I've seen that supposedly answer the question say to do things but not where to do them. I'm looking for something clear and concise here.
What am I missing here, or am I going about getting the current user all wrong?
The WCF client throws the "The provided URI scheme 'http' is invalid; expected 'https' " error because the basicHttpBinding specifies security mode="Transport"> but the endpoint address currently indicates a non-secure protocol (endpoint address="http://localhost:57165/MyService.svc". Update the endpoint address to match the binding's security to resolve the communication issue.
Once the transport error is resolved, you can then focus on the proper security binding and the code needed to extract the user information. In order to get the username, your binding will need to specify a client credential type of username. Your service can then access the user information by using System.ServiceModel.ServiceSecurityContext through the OperationContext.
The following links may be useful:
http://www.codemag.com/article/0611051
http://msdn.microsoft.com/en-us/library/ms731058%28v=vs.110%29.aspx
http://msdn.microsoft.com/en-us/library/system.servicemodel.servicesecuritycontext(v=vs.110).aspx
In order to get windows credentials out of your wcf webservice, your binding has to be a wsHttpBinding:
<wsHttpBinding>
<binding name="wsHttpBinding_MyService">
<security mode="TransportCredentialOnly" />
</binding>
</wsHttpBinding>
This is an example of a working security that transports the credentials.

The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'NTLM'

Few days ago I had quite a headache with authentication problems when using Windows authentication between client and wcf web service. The error I was getting was "The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was "NTLM". None of the solutions on stack worked because most of them were related to old methods.
THE ANSWER: The problem was all of the posts for such an issue were related to older kerberos and IIS issues where proxy credentials or AllowNTLM properties were helping. My case was different. What I have discovered after hours of picking worms from the ground was that somewhat IIS installation did not include Negotiate provider under IIS Windows authentication providers list. So I had to add it and move up. My WCF service started to authenticate as expected. Here is the screenshot how it should look if you are using Windows authentication with Anonymous auth OFF.
You need to right click on Windows authentication and choose providers menu item.
Hope this helps to save some time.
I have upgraded my older version of WCF to WCF 4 with below changes, hope you can also make the similar changes.
1. Web.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="Demo_BasicHttp">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="InheritedFromHost"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="DemoServices.CalculatorService.ServiceImplementation.CalculatorService" behaviorConfiguration="Demo_ServiceBehavior">
<endpoint address="" binding="basicHttpBinding"
bindingConfiguration="Demo_BasicHttp" contract="DemoServices.CalculatorService.ServiceContracts.ICalculatorServiceContract">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="Demo_ServiceBehavior">
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<!-- To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add scheme="http" binding="basicHttpBinding" bindingConfiguration="Demo_BasicHttp"/>
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
2. App.config:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_ICalculatorServiceContract" maxBufferSize="2147483647" maxBufferPoolSize="33554432" maxReceivedMessageSize="2147483647" closeTimeout="00:10:00" sendTimeout="00:10:00" receiveTimeout="00:10:00">
<readerQuotas maxArrayLength="2147483647" maxBytesPerRead="4096" />
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Ntlm" proxyCredentialType="None" realm="" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:24357/CalculatorService.svc" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_ICalculatorServiceContract" contract="ICalculatorServiceContract" name="Demo_BasicHttp" />
</client>
</system.serviceModel>
Not this exact problem, but this is the top result when googling for almost the exact same error:
If you see this problem calling a WCF Service hosted on the same machine, you may need to populate the BackConnectionHostNames registry key
In regedit, locate and then click the following registry subkey: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0
Right-click MSV1_0, point to New, and then click Multi-String Value.
In the Name column, type BackConnectionHostNames, and then press ENTER.
Right-click BackConnectionHostNames, and then click Modify.
In the Value data box, type the CNAME or the DNS alias, that is used for the local shares on the computer, and then click OK.
Type each host name on a separate line.
See Calling WCF service hosted in IIS on the same machine as client throws authentication error for details.
For me the solution was besides using "Ntlm" as credential type:
XxxSoapClient xxxClient = new XxxSoapClient();
ApplyCredentials(userName, password, xxxClient.ClientCredentials);
private static void ApplyCredentials(string userName, string password, ClientCredentials clientCredentials)
{
clientCredentials.UserName.UserName = userName;
clientCredentials.UserName.Password = password;
clientCredentials.Windows.ClientCredential.UserName = userName;
clientCredentials.Windows.ClientCredential.Password = password;
clientCredentials.Windows.AllowNtlm = true;
clientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
}
The solution for me was to set the AppPool from using the AppPoolIdentity to the NetworkService identity.
I had the same problem, to solve it set specific user from domain in iis -> action sidebar->Basic Settings -> Connect as... -> specific user

Is TransportWithMessageCredential without certificate secure enough for a WCF service?

I have developed a WCF self-hosted service, for which I have two basic security requirements as it will be accessed over the Internet:
The transport layer should prevent tampering and sniffing, especially the retrieval of authentication credentials. This is what SSL does, but from what I have seen setting up SSL requires the installation of certificates (except maybe through this hack that uses plain certificate files), which I prefer not to have to do.
The authentication layer should consist of a username/password validator.
I configured my service to use:
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
<transport clientCredentialType="Basic" />
</security>
Even if the transport layer is HTTP (not HTTPS), does this make WCF create another security layer that is equivalent to SSL? If not, what is the difference in terms of security strength?
Also, is there any way to secure the meta data endpoint without using a SSL certificate (not essential but would be appreciated)?
Here is my full configuration code for the self-hosted service:
<?xml version="1.0"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup>
<system.serviceModel>
<services>
<service name="MyService">
<host>
<baseAddresses>
<add baseAddress = "http://localhost:8000/Services" />
</baseAddresses>
</host>
<endpoint address ="MyService" binding="wsHttpBinding" contract="IMyService">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="Binding1" maxReceivedMessageSize="2147483647">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName" />
<transport clientCredentialType="Basic" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="True"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="CR.Common.Services.CustomValidator, Common" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Thank you!
By default, all secure WCF bindings (like wsHttpBinding) will encrypt and sign messages.
SSL mandatory use a certificate, and the hack in the link you give is hacking wcf, not SSL. Because without SSL WCF forbid the use of the basicHttpBinding (which send xml in clear) and UserNamePasswordValidator, because in this case anyone that intercept the message can get the username/password.
With WSHttpBinding you could avoid SSL and put the security on the message level.
I strongly advise you to read this article, especially the Service Credentials and Negotiation chapter:
To support mutual authentication and message protection, services must
provide credentials to the caller. When transport security is used
(SSL), service credentials are negotiated through the transport
protocol. Service credentials for message security can also be
negotiated when Windows credentials are used; otherwise a service
certificate must be specified
With the UserNamePasswordValidator, you must configure a certificate on the server to allow the client the sign and encrypt each message (using the certificate's public key).
If you were using Windows authentication, it'll not be needed.
Why are you so worried about certificate ?

Categories

Resources