WCF 4 REST - Multiple Standard Endpoints for Authentication - c#

So I'm attempting to configure a WCF 4 REST app to utilize multiple standard endpoints (for the help functionality). The reason for this is that my hosting IIS process has both Anonymous and Windows Authentication enabled, and certain endpoints within my WCF app require one or the other (both results in an exception).
Previously, I was able to do this by defining some bindings:
<bindings>
<webHttpBinding>
<binding name="Anonymous">
<security mode="None" />
</binding>
<binding name="WindowsAuthentication">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
</webHttpBinding>
</bindings>
And then defining the services like so:
<services>
<service name="Host.SubscriberInfoHost">
<endpoint address="" binding="webHttpBinding" bindingConfiguration="WindowsAuthentication" contract="Host.ISubscriberInfoHost" />
</service>
<service name="Utilities.Instrumentation.ServiceStatus.ServiceStatusHost">
<endpoint address="" binding="webHttpBinding" bindingConfiguration="Anonymous" contract="Utilities.Instrumentation.ServiceStatus.IServiceStatusHost" />
</service>
</services>
This is what I've tried to do so far while utilizing the standard endpoints model:
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="Host.SubscriberInfoHost" helpEnabled="true" automaticFormatSelectionEnabled="true">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</standardEndpoint>
<standardEndpoint name="Utilities.Instrumentation.ServiceStatus.IServiceStatusHost" helpEnabled="true" automaticFormatSelectionEnabled="true">
<security mode="None" />
</standardEndpoint>
</webHttpEndpoint>
</standardEndpoints>
However doing this gets the service confused, as I receive:
System.InvalidOperationException: IIS specified authentication schemes 'Negotiate, Anonymous', but the binding only supports specification of exactly one authentication scheme. Valid authentication schemes are Digest, Negotiate, NTLM, Basic, or Anonymous. Change the IIS settings so that only a single authentication scheme is used
Which is exactly what I'm trying to get away from. Could anyone possibly give me a hand on how I would set this situation up using the new standard endpoints model? Thanks!

Found the answer to this after some experimentation. It turns out that the "name" attribute for standard endpoints is actually an endpoint configuration. So, you would use the following standard endpoints:
<standardEndpoint name="WindowsAuthentication" helpEnabled="true" automaticFormatSelectionEnabled="true">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</standardEndpoint>
<standardEndpoint name="Anonymous" helpEnabled="true" automaticFormatSelectionEnabled="true">
<security mode="None" />
</standardEndpoint>
And then, you would also configure a service such as the following (the "kind" and "endpointConfiguration" attributes must be set in order to tie this endpoint to the standard endpoint above)
<service name="SomeEndpoint">
<endpoint address="" kind="webHttpEndpoint" endpointConfiguration="WindowsAuthentication" contract="ISomeEndpoint" />
</service>
This allows you to mix authentication styles while maintaining the handy service help page.

Related

Windows Service could not connect to WCF Service when installed on the same web server

I have to provide the userPrincipalName if the windows service is installed on the same web server but I can remove the identity tag if the windows service is installed on any other machine. what could be the reason of such behaviour?
NOTE: Application pool is running under custom domain account
Here is my Windows Service app.config settings:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_ITimerJobService">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="Windows" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="https://domain/TimerJobService.svc"
binding = "wsHttpBinding" bindingConfiguration = "WSHttpBinding_ITimerJobService" contract="TimerJob.ITimerJobService" name= "WSHttpBinding_ITimerJobService">
<identity>
<userPrincipalName value="ApplicationPoolIdentity"/>
</identity>
</endpoint>
</client>
</system.serviceModel>
Thank you very much.

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.

Publish a non https endpoint for WCF - Azure

How can I add a non https endpoint for a WCF ? My WCF is a web role in an Azure project.
My current endpoint is :
<bindings>
<basicHttpBinding>
<binding name="SecureBasic" proxyAddress="http://localhost:80">
<security mode="Transport">
<transport clientCredentialType="None"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="WebRoleUploadImages.UploadImages">
<host>
<baseAddresses>
<add baseAddress="http://localhost:80"/>
</baseAddresses>
</host>
<endpoint address="WCFSecure" binding="basicHttpBinding" bindingConfiguration="SecureBasic"
name="SecureHTTPSendpoint" contract="WebRoleUploadImages.IUploadImages"> </endpoint>
</service>
</services>
The thing is I do not yet have an SSL certificate so I cannot test my app without an http endpoint
This isn't an azure limitation, but a WCF limitation. Basic authentication sends the password in plain text, and doing that without an SSL certification is a horribly bad idea. If it's only for testing you can cheat it by specifying
<security mode="TransportCredentialOnly">

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.

Sporadic exceptions calling a web service that is load balanced

I have a web service that I am running on three load balanced web servers and I am getting sporadic errors. Now, I admit that the load balanced part may be a bit of a red herring, but when I test with only 1 web server I cannot reproduce the error. If I test with all three web servers I can get the error (but it is not 100% of the time, more like 50%). All testing is done through the load balancer, we just tell the load balancer how many servers we want to farm.
The code is simple single request code. That is, there is no state. A request is made and a response is returned. The web service code is c# .NET 4 running on IIS 7.5. The client code is both a web site and a desktop app.
I get one of two exceptions:
System.ServiceModel.Security.MessageSecurityException:
An unsecured or incorrectly secured
fault was received from the other
party. See the inner FaultException
for the fault code and detail. --->
System.ServiceModel.FaultException:
The security context token is expired
or is not valid. The message was not
processed.
Or I get:
System.ServiceModel.Security.SecurityNegotiationException:
Secure channel cannot be opened
because security negotiation with the
remote endpoint has failed. This may
be due to absent or incorrectly
specified EndpointIdentity in the
EndpointAddress used to create the
channel. Please verify the
EndpointIdentity specified or implied
by the EndpointAddress correctly
identifies the remote endpoint. --->
System.ServiceModel.FaultException:
The request for security token has
invalid or malformed elements.
As you can see from the following snips from my .config files, I am not using security as this is strictly an internal web service. (names have been changed to protect the innocent--namely me).
Server Side:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- Service Side web.config -->
...
<system.serviceModel>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<services>
<service behaviorConfiguration="InternalUseOnly.InternalUseOnlyServiceBehavior" name="InternalUseOnly.InternalUseOnlyService">
<endpoint address="" bindingNamespace="http://somecompany.com/webservices" binding="wsHttpBinding" contract="InternalUseOnly.IInternalUseOnlyService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="InternalUseOnly.InternalUseOnlyServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
...
</configuration>
Client side
<?xml version="1.0" encoding="UTF-8"?>
<!-- Client Side web.config -->
<configuration>
...
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IInternalUseOnlyService" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true" algorithmSuite="Default" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://intranet.somecompany.com/InternalUseOnly/InternalUseOnlyService.svc" binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IInternalUseOnlyService" contract="InternalUseOnlyService.IInternalUseOnlyService" name="WSHttpBinding_IInternalUseOnlyService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
</system.serviceModel>
...
</configuration>
Thoughts anyone?
Additional information: After reviewing the answers below I have tried two things, both without success.
The most obvious change (which I did not notice at first) was to change one of properties on the client to allow cookies <system.serviceModel><bindings><wsHttpBinding><binding name="blah, blah, blah" ... other properties... allowCookies="true" /> It defaults to false. Further, our load balancer uses cookies to keep affinity. But, it did not make a difference (no clue why yet).
Next, I tried various security options in the client side app.config file. This included both <security mode="None" /> and a more elaborate:
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None" />
<message clientCredentialType="None" establishSecurityContext="false" negotiateServiceCredential="false"/>
</security>
although the settings in the last one was just a guess on my part. I did not make any server side changes to the app.config as I don't know what to change and, sadly, I can only test with production as we only have 1 dev web server, not three.
I am going to go out on a limb here and guess that the security involved is the Message security specified on the client side:
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true" algorithmSuite="Default" />
</security>
If you are creating a client and connecting, the negotiated windows credential token may be cached. If you don't have sticky sessions enabled, the token might be passed back to the wrong server and will fail. My guess is that its always on the second call?
It is an NTLM problem caused by using load balancers without sticky sessions. To correct the problem you need to configure session affinity (sticky session). If you don't you will get a failure because part of the NTLM handshake happened on one server and the other part happens on another server.
While Chris and Jeff have help get me on the track to an answer, what actually solved it for me was this article i found from Microsoft on Load Balancing Web Services. In short, what we had to do to resolve this for our web farm was to switch from the default wsHttpBinding to basicHttpBinding. This was not difficult, but was an all-or-nothing move. The main web service and every client had to be reconfigured at the same time or it would break.
While wsHttpBinding does have a property of allowCooikes that could be set to true, it apparently does not use them until after the connection is made, at which point the request could jump servers on the first request and thus fail.

Categories

Resources