Delegate IIS Application Pool account to WCF service calls - c#

I have developed a WCF service which is running in IIS (IIS 7.5 to be exact). This service runs under its own app pool, under a specific domain identity. This service references & calls other WCF services hosted elsewhere in the network, which in turn access various resources (Event Log, SQL Servers etc).
Calls to my service are authenticated using username & password, through a custom UserNamePasswordValidator. The username(s) used are not domain credentials.
What I'm trying to do, is that when my service is called & it in turn calls the referenced services using the generated proxy classes, that it delegates the application pool identity as the calling identity, since this domain account has been granted rights to access the background resources like SQL Server.
My current implementation is as follows:
Service Configuration
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="RemoteServiceBinding" closeTimeout="00:10:00"
openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00"
maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647">
<readerQuotas maxStringContentLength="2147483647" maxArrayLength="2147483647"
maxBytesPerRead="2147483647" />
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
<wsHttpBinding>
<binding name="MyServiceBinding" closeTimeout="00:10:00" openTimeout="00:10:00"
receiveTimeout="00:10:00" sendTimeout="00:10:00" maxBufferPoolSize="2147483647"
maxReceivedMessageSize="2147483647">
<readerQuotas maxStringContentLength="2147483647" maxArrayLength="2147483647"
maxBytesPerRead="2147483647" />
<security mode="Message">
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://remote.service.address/Service.svc"
binding="basicHttpBinding" bindingConfiguration="RemoteServiceBinding"
contract="RemoteService.IRemoteService" name="RemoteServiceBinding" />
</client>
<services>
<service name="MyService.MyService" behaviorConfiguration="MyServiceBehavior">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="MyServiceBinding" contract="MyService.IMyService">
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8733/MyService/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MyServiceBehavior">
<serviceMetadata httpGetEnabled="True" httpsGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="True" />
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="None" />
</clientCertificate>
<serviceCertificate findValue="AuthCert" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyService.CredentialValidator, MyService" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
Service behavior code
using (var client = new Proxy.RemoteServiceClient()) {
client.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Delegation;
return client.PerformAction();
}
Using this code, whenever a client makes a call to my service, the following is thrown:
The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate,NTLM'.
Could someone please assist me, or point me in the right direction on how to implement this authentication configuration?

I've managed to find a working solution. It is implemented as such:
The client proxy credentials need to be set to those of the IIS Application Pool, since these don't get picked up automatically:
client.ClientCredentials.Windows.ClientCredential = System.Net.CredentialCache.DefaultNetworkCredentials;
Also, the remote service I was connecting to had a service principal that needed to be included in the endpoint configuration. So I modified the config that was generated by the VS tooling to the following:
<client>
<endpoint address="http://remote.service.address/Service.svc"
binding="basicHttpBinding" bindingConfiguration="RemoteServiceBinding"
contract="RemoteService.IRemoteService" name="RemoteServiceBinding">
<identity>
<servicePrincipalName value="spn_name" />
</identity>
</endpoint>
</client>
With this configuration, I was able to authenticate to my service by username & password, then have my service access a SQL Server instance using the domain credentials that the application pool was running under in IIS.

Related

Could not find a base address that matches scheme net.tcp for the endpoint with binding NetTcpBinding. Base address schemes are [http]

I have this configuration for my WCF service which runs on IIS Express port number 50187. The service is hosted on IIS Express of Visual Studio 2017:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="QCConsumerBinding" closeTimeout="00:10:00" openTimeout="00:10:00" receiveTimeout="00:10:00" sendTimeout="00:10:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="50000000" maxBufferPoolSize="5242880" maxReceivedMessageSize="50000000" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" 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>
<services>
<service name="QCWCFService.QCService">
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="QCConsumerBinding" contract="QCWCFService.IQCService" />
</service>
<service name="QCWCFService.QCFinalService">
<endpoint address="" binding="basicHttpBinding" bindingConfiguration="QCConsumerBinding" contract="QCWCFService.IQCFinalService" />
</service>
<service name="QCWCFService.CalibrationService">
<endpoint address="service" binding="netTcpBinding" contract="QCWCFService.ICalibrationService" />
<endpoint address="" binding="wsDualHttpBinding" contract="QCWCFService.ICalibrationService" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8080/CalibrationService" />
<add baseAddress="http://localhost:8081/CalibrationService" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- 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="true" />
<dataContractSerializer maxItemsInObjectGraph="2147483647" />
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add scheme="http" binding="wsDualHttpBinding" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
But when I try to run the service it gives this exception:
System.InvalidOperationException: Could not find a base address that matches scheme net.tcp for the endpoint with binding NetTcpBinding. Registered base address schemes are [http].
While I have another application with the same configuration for Dual Http Binding which works perfectly
By default, The IIS express doesn’t support Net.tcp protocol.
The service endpoint with Nettcpbinding requires a base address based on NetTcp protocol.
<endpoint address="service" binding="netTcpBinding" contract="QCWCFService.ICalibrationService" />
Although we provide a Nettcp base address by using the Host Section, It won’t work. This is due to the fact IIS express use self-configuration to provide a base address to run the current project. The configuration of IIS express usually located in the .vs folder of the current Solution, called applicationhost.config
If we run this project in a console application with this configuration, it will work. Thereby we should provide one base address with Nettcp protocol. This can be completed in IIS.
1. Enable windows feature for net.tcp protocol.
2. Add Net.tcp support on the website.
3. Add net.tcp protocol in site binding module.
Please refer to the below for details of adding net.tcp protocol to a website.
WCF ContractFilter Mismatch when enabling Reliable Session
Feel free to let me know if the problem still exists.

WCF Site with multiple services and contracts

I am running into an issue trying to host two services on the same site. They have the same base uri, but different services names, and each has its own contract.
While testing in my VS environment (IIS 7.5) everything works fine. However when I deploy to a server (IIS 8.5), both uri's are showing the same wsdl for some reason. It seems like the contract for the second service is ignored.
There are two different .svc files with code behind. (all names have been changed to protect the innocent.)
sites:
https://mysite/services/Service1.svc
https://mysite/services/Service2.svc
Here is my config:
<services>
<service name="Service1" behaviorConfiguration="DefaultBehavior">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="DefaultBinding" contract="Namespace.IService1"/>
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/>
</service>
<service name="Service2" behaviorConfiguration="DefaultBehavior">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="DefaultBinding" contract="Namespace.IService2"/>
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/>
</service>
</services>
<bindings>
<wsHttpBinding>
<binding name="DefaultBinding" receiveTimeout="00:05:00"
sendTimeout="00:05:00" bypassProxyOnLocal="false" transactionFlow="false"
hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="2147483647"
maxReceivedMessageSize="2147483647" messageEncoding="Mtom"
textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
<readerQuotas maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
<reliableSession ordered="true" inactivityTimeout="00:5:00" enabled="false"/>
<security mode="Transport">
<transport clientCredentialType="None" />
</security>
</binding>
<wsHttpBinding>
<bindings>
<serviceBehaviors>
<behavior name="DefaultBehavior">
<serviceMetadata httpsGetEnabled="true" httpsGetBindingConfiguration="true" />
<serviceDebug httpHelpPageEnabled="true" includeExceptionDetailInFaults="true" />
<dataContractSerializer maxItemsInObjectGraph="2147483647"/>
<serviceThrottling maxConcurrentCalls="160" maxConcurrentInstances="160" maxConcurrentSessions="100" />
</behavior>
</serviceBehaviors>
The problem is that both sites are reflecting the same WSDL, i.e. same methods for contract="Namespace.IService1" Any ideas on what is going on here to cause this?
I believe the problem is because they both have the same address.
How can the server distinguish if your intent is to call Service1 or Service2?
While keeping the same base URI try to change their address
Unfortunately, WCF doesn't really have a good way to handle this use case. You can't have two contracts on the same endpoint
The address for the endpoint for Service1
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="DefaultBinding" contract="Namespace.IService1"/>
The address must be different from that of Service2
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="DefaultBinding" contract="Namespace.IService2"/>
It actually turned out to be something stupid, an xcopy error. My xcopy script for the new additional service was copied and pasted from the original service, but I forgot to rename the .svc source to the new service name. So it copied the same .svc to two different files.

WebAPI Certificates and Authentication - ELI5

I'm sure I'm missing some key facts, but I can get those for you. I'm really confused on how this all needs to work:
Server 1 – IIS 8, Hosting a Vendor’s WebAPI, Anonymous and Windows Authentication. Providers are Negotiate, NTLM. Https and a signed certificate (Cert1).
Server 2 – IIS8, New WebAPI connecting to Server1’s WebAPI. I’m assuming I need to store Cert1 on Server 2. We will have another Certificate, https (Cert2)
Server 3 – IIS 8, Website connecting to server 2’s webAPI.
User – Browser connecting to Server 3, Windows Authentication Only.
Every server and the user’s browser connects to the same Active Directory.
I have access to Server1’s web.config to change bindings, but not the code. In Visual Studio 2013, when I add the service reference for Server 2, the web.config is added like this:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_ICoreWebService">
<security mode="Transport">
<transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
<message clientCredentialType="Certificate" negotiateServiceCredential="true" algorithmSuite="Default" establishSecurityContext="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://dave.domain.com/webService/CoreWebService.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ICoreWebService" contract="Dave.ICoreWebService" name="WSHttpBinding_ICoreWebService">
<identity>
<userPrincipalName value="Server1ServiceAccount#dave.domain.com" />
</identity>
</endpoint>
</client>
This is Server1's WebAPI web.config
<system.serviceModel>
<bindings>
<wsHttpBinding>
<!-- The following block is used for secure connection (HTTPS)-->
<binding name="DaveServiceBinding" maxReceivedMessageSize="2147483647" maxBufferPoolSize="2147483647" receiveTimeout="00:05:00" sendTimeout="00:05:00">
<readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" />
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="Dave.WebService.CoreWebService" behaviorConfiguration="DaveWebServiceBehavior">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="DaveWebServiceBinding" contract="Dave.WebService.ICoreWebService" />
<endpoint address="wauth" binding="wsHttpBinding" bindingConfiguration="DaveWebServiceBindingWauth" contract="Dave.WebService.ICoreWebService" />
<endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="DaveWebServiceBehavior">
<dataContractSerializer maxItemsInObjectGraph="2147483647" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Dave.WebService.WebServiceAuthValidator,Dave.WebService" />
</serviceCredentials>
<serviceMetadata httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
I’m having trouble with how the certificates work between Server 1 and Server 2. I just need to download Cert1 and store it on Server2? Then refer to that certificate when I make the call. This code isn't finding the certificate:
svc.ClientCredentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine,
StoreName.TrustedPublisher,
X509FindType.FindBySubjectName, "CN = dave.domain.com, OU = ZZ123, O = Dave, Inc., L = Chicago, S = Illinois, C = US");
How can I bubble up the windows authentication from the user to server1? The vendor’s API will Authenticate through that message.
Right now, I’m able to browse to the service locally, but I’ve been stuck on Server 2 and getting the certificate. I want to make sure I’m storing and referencing it correctly.
Thanks in Advance.
The Subject Name needed to be the domain, and I used the MMC to make sure the certificate was where it should be.
MMC -> certificates (you may need to add them in your snap-in)
Trusted People -> Certificates.
Hope that helps someone.

WCF http binding error: The content type text/html does not match the content type of the binding (text/xml; charset=utf-8)

I have a WPF client which consumes a WCF service which is self hosted in a Winforms app. The client is accessing the service server via a VPN connection. At first initialisation, the client app catches an exception from the service:
There was a problem reaching the service.
The content type of text/html of the response message does not match the content type of the binding (text/xml;charset=utf-8).
If using a custom encoder, be sure that the IsContentTypeSupported method is
implemented properly.
The service is running without issue and this issue only appears when connecting via VPN, not from my Visual Studio development environment which is on the same domain as the service VM:
This exception only occurs on first initialisation, when I run the client app again, the issue is resolved & everything runs as expected. Here is my service app config:
<services>
<service name="IsesService.IsesService">
<endpoint address="" binding="basicHttpBinding" contract="IsesService.IIsesService" bindingConfiguration="basicHttp">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://EMEA-DIIS01v:8082"/>
</baseAddresses>
</host>
</service>
</services>
<behavior>
<serviceMetadata httpGetEnabled="True" httpsGetEnabled="True" />
<dataContractSerializer maxItemsInObjectGraph="2147483646" />
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
<bindings>
<basicHttpBinding>
<binding name="basicHttp"
useDefaultWebProxy="false"
maxReceivedMessageSize="2147483647"
maxBufferSize="2147483647"
maxBufferPoolSize="2147483647"
>
<readerQuotas maxArrayLength="2147483647"
maxBytesPerRead="2147483647"
maxDepth="2147483647"
maxNameTableCharCount="2147483647"
maxStringContentLength="2147483647"/>
</binding>
</basicHttpBinding>
</bindings>
And client side:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IIsesService" useDefaultWebProxy="false"
maxReceivedMessageSize="2147483647"
maxBufferSize="2147483647"
maxBufferPoolSize="2147483647">
<readerQuotas maxArrayLength="2147483647"
maxBytesPerRead="2147483647"
maxDepth="2147483647"
maxNameTableCharCount="2147483647"
maxStringContentLength="2147483647"/>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://emea-diis01v:8082/" binding="basicHttpBinding"
bindingConfiguration="BasicHttpBinding_IIsesService" contract="ServiceReference.IIsesService"
name="BasicHttpBinding_IIsesService" />
</client>
</system.serviceModel>
Am I missing something simple here?
Check with fiddler what is the content of the first response.
It is probably different when you use VPN.

WCF authentication schemes mismatch?

I am using .Net 3.5 and attempting to configure a WCF service and am receiving the exception, The HTTP request is unauthorized with client authentication scheme 'Negotiate'. The authentication header received from the server was 'Negotiate,NTLM'. I have attached my server-side and client-side .config files below.
Just a couple of notes. The application and service are both using impersonation due to network access requirements. The web application resides on a different server than the WCF service. Both also have the following specified in their respective web.config files.
<authentication mode="Windows"/>
<identity impersonate="true" userName="userName" password="password"/>
Web Application (on server1)
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IReports" 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="false" proxyAddress="http://server2/Services/ReportService">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint name="BasicHttpBinding_IReports" address="http://server2/Services/ReportService/Reports.svc"
binding="basicHttpBinding" contract="WCFServiceRef.IReports" bindingConfiguration="BasicHttpBinding_IReports"
behaviorConfiguration="ClientBehavior"/>
</client>
<behaviors>
<endpointBehaviors>
<behavior name="ClientBehavior" >
<clientCredentials supportInteractive="true" >
<windows allowedImpersonationLevel="Impersonation" allowNtlm="true" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
WCF Service (on server2)
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
<bindings>
<basicHttpBinding>
<binding name="default" maxReceivedMessageSize="200000">
<readerQuotas maxStringContentLength="200000" maxArrayLength="200000"/>
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="ReportService.ReportsBehavior" name="ReportService.Reports">
<endpoint address="" binding="basicHttpBinding" contract="ReportService.IReports" bindingConfiguration="default">
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint name="mex" address="mex" binding="basicHttpBinding" contract="IMetadataExchange" bindingConfiguration="default"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ReportService.ReportsBehavior">
<serviceAuthorization impersonateCallerForAllOperations="false"/>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
I thought that if I were to apply the allowNtlm="true" directive in the application that this would be fixed. It seems to me that the server is expecting Windows authentication but is not receiving it? Due to the application and service residing on different servers do I need to use the proxy values? I feel that I'm not understanding something basic but whether it's on the server-side IIS configuration or simply in my application I don't know.
Thanks for any help!
This sample from MSDN for basicHttpBinding with TransportCredentialOnly shows how to set it up. Your config is very similar except that it is also setting message level security. I'd try removing the message element from the config to see if that is the cause of the problem.
I don't believe the problem is passing the impersonation credentials themselves but the TransportCredentialOnly configuration. Also, make sure IIS is configured to support Windows authentication on the WCF server.

Categories

Resources