dovla110010101
dovla110010101

Reputation: 431

WCF doesn't work with disabled anonymous authentication option in IIS

I made very simple WCF app and found one problem which I cannot resolved. regarding WCF service which is actually working if the Anonymous Authentication is enabled but when I disable this feature on IIS it gives me an error: The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was ''

IIS configuration

this is web.config configuration:

<?xml version="1.0"?>
<configuration>

<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5"/>
</system.web>
<system.serviceModel>
<services>
  <service name="WCFTestApplication.WCFTestApplication">
    <endpoint address="" 
              binding="wsHttpBinding"
              bindingConfiguration="WCFTestAppBinding" 
              contract="WCFTestApplication.IWCFTestApplication" 
     />
    <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
  </service>
  </services>
  <bindings>
  <wsHttpBinding>
    <binding messageEncoding="Text" name="WCFTestAppBinding">
      <security mode="TransportWithMessageCredential">
        <message clientCredentialType="Windows"/>
        <transport clientCredentialType="Windows" proxyCredentialType="Windows"/>
      </security>
    </binding>
  </wsHttpBinding>
  </bindings>
  <behaviors>
  <serviceBehaviors>
    <behavior>
      <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
      <serviceMetadata httpGetEnabled="false" 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 binding="basicHttpsBinding" scheme="https" />
  </protocolMapping-->    
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
 </system.serviceModel>
 <system.webServer>
 <validation validateIntegratedModeConfiguration="false"/>
 <modules runAllManagedModulesForAllRequests="true"/>
  <!--
    To browse web app root directory during debugging, set the value below to true.
    Set to false before deployment to avoid disclosing web app folder information.
  -->
  <directoryBrowse enabled="false"/>
 </system.webServer>

this is client application source:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Net;

namespace WCFClientApplication
{
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private string _name = "";
    private string _passwd = "";
    private string _domain = "";

    public string UserName
    {
        get { return _name; }
        set { _name = value; }
    }

    public string Password
    {
        get { return _passwd; }
        set { _passwd = value; }
    }

    public string Domain
    {
        get { return _domain; }
        set { _domain = value; }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        //if (!String.IsNullOrEmpty(usrTxt.Text) || !String.IsNullOrEmpty(passTxt.Text) || !String.IsNullOrEmpty(domainTxt.Text))
        //{
            UserName = usrTxt.Text;
            Password = passTxt.Text;
            Domain = domainTxt.Text;

            WCFClientProxy.WCFTestApplicationClient client = new WCFClientProxy.WCFTestApplicationClient();
            client.ClientCredentials.Windows.ClientCredential = new System.Net.NetworkCredential(UserName, Password, Domain);
            //System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) => { return true; };
            textBox1.Text = client.GetData();
        //}
        //else
        //{
        //    MessageBox.Show("Fields like username, password and domain must not be blank...!","Warning...!",MessageBoxButtons.OK, MessageBoxIcon.Warning);
        //}
    }

    private void domainTxt_MouseHover(object sender, EventArgs e)
    {
        tipLbl.Visible = true;
    }

    private void domainTxt_MouseLeave(object sender, EventArgs e)
    {
        tipLbl.Visible = false;
    }
}
}

wcf interface:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WCFTestApplication
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together.
[ServiceContract]
public interface IWCFTestApplication
{

    [OperationContract]
    string GetData();

    // TODO: Add your service operations here
}
}

Class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WCFTestApplication
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
// NOTE: In order to launch WCF Test Client for testing this service, please select Service1.svc or Service1.svc.cs at the Solution Explorer and start debugging.
public class WCFTestApplication : IWCFTestApplication
{
    public string GetData()
    {
        return "WCF is working, and message is authenticated and encrypted";
    }
}
}

I read somewhere that anonymous authentication has something with MEX endpoint and communication, but how then I can retain windows login, and disable anonymous user login so WCF cannot be used whithout proper credentials?

Upvotes: 1

Views: 3382

Answers (1)

dovla110010101
dovla110010101

Reputation: 431

I finally found solution:

Web.config

<?xml version="1.0"?>
<configuration>

<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<authentication mode="Windows" />
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5"/>
</system.web>
<system.serviceModel>
<services>
  <service name="WCFTestApplication.WCFTestApplication">
    <endpoint address="" 
              binding="wsHttpBinding"
              bindingConfiguration="WCFTestAppBinding" 
              contract="WCFTestApplication.IWCFTestApplication"
     />
    <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange" />
  </service>
</services>
<bindings>
  <wsHttpBinding>
    <binding messageEncoding="Text" name="WCFTestAppBinding">
      <security mode="Transport">
        <transport clientCredentialType="Ntlm"/>
      </security>
    </binding>
  </wsHttpBinding>
</bindings>
<behaviors>
  <serviceBehaviors>
    <behavior>
      <serviceCredentials>
        <windowsAuthentication allowAnonymousLogons="false" includeWindowsGroups="true"/>
      </serviceCredentials>
      <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
      <serviceMetadata httpGetEnabled="false" 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="true"/>
    </behavior>
  </serviceBehaviors>
</behaviors>
<!--protocolMapping>
    <add binding="basicHttpsBinding" scheme="https" />
</protocolMapping-->    
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<modules runAllManagedModulesForAllRequests="true"/>
<!--
    To browse web app root directory during debugging, set the value below to true.
    Set to false before deployment to avoid disclosing web app folder information.
  -->
<directoryBrowse enabled="false"/>
</system.webServer>
</configuration>

app.config

<?xml version="1.0"?>
<configuration>
<configSections>
</configSections>
<startup>
  <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
</startup>
<system.serviceModel>
    <bindings>
        <wsHttpBinding>
            <binding name="WSHttpBinding_IWCFTestApplication">
                <security mode="Transport">
                    <transport clientCredentialType="Ntlm" />
                </security>
            </binding>
        </wsHttpBinding>
    </bindings>
    <client>
        <endpoint address="https://vladimir.intra.jv.hr/WCFTestApplication.svc"
            binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IWCFTestApplication"
            contract="WCFTestApplication.IWCFTestApplication" name="WSHttpBinding_IWCFTestApplication">
            <identity>
                <servicePrincipalName value="host/vladimir.intra.jv.hr" />
            </identity>
        </endpoint>
    </client>
   </system.serviceModel>
  </configuration>

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using System.Net;

namespace WCFClientApplication
{
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private string _name = "";
    private string _passwd = "";
    private string _domain = "";

    public string UserName
    {
        get { return _name; }
        set { _name = value; }
    }

    public string Password
    {
        get { return _passwd; }
        set { _passwd = value; }
    }

    public string Domain
    {
        get { return _domain; }
        set { _domain = value; }
    }

    private void button1_Click(object sender, EventArgs e)
    {
        //if (!String.IsNullOrEmpty(usrTxt.Text) || !String.IsNullOrEmpty(passTxt.Text) || !String.IsNullOrEmpty(domainTxt.Text))
        //{

            UserName = usrTxt.Text;
            Password = passTxt.Text;
            Domain = domainTxt.Text;

            WCFTestApplication.WCFTestApplicationClient client = new WCFTestApplication.WCFTestApplicationClient();

            client.ClientCredentials.Windows.ClientCredential.Domain = Domain;
            client.ClientCredentials.Windows.ClientCredential.UserName = UserName;
            client.ClientCredentials.Windows.ClientCredential.Password = Password;
            //this part needs to be modified, so certificate can be accepted from other machines as well. 
            System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) => { return true; };
            textBox1.Text = client.GetData();
            client.Close();
        //}
        //else
        //{
        //    MessageBox.Show("Fields like username, password and domain must not be blank...!","Warning...!",MessageBoxButtons.OK, MessageBoxIcon.Warning);
        //}
    }

    private void domainTxt_MouseHover(object sender, EventArgs e)
    {
        tipLbl.Visible = true;
    }

    private void domainTxt_MouseLeave(object sender, EventArgs e)
    {
        tipLbl.Visible = false;
    }
    }
}

Interface

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WCFTestApplication
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "IService1" in both code and config file together.
[ServiceContract]
public interface IWCFTestApplication
{

    [OperationContract]
    string GetData();

    // TODO: Add your service operations here
}
}

Class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;

namespace WCFTestApplication
{
// NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in code, svc and config file together.
// NOTE: In order to launch WCF Test Client for testing this service, please select Service1.svc or Service1.svc.cs at the Solution Explorer and start debugging.
public class WCFTestApplication : IWCFTestApplication
{
    public string GetData()
    {
        return "WCF is working, and message is authenticated and encrypted";
    }
}
}

I pasted entire code so the others can study a bit and find everything that is necessary for creating their own configuration. So I this is setup that includes SSL, wsHttpBinding and Windows/NTLM authentication to web service.

The thing is, I was going in totally different direction, wasting time on IIS configuration and trying to bust my chops why authentication didn't work at the first place when I disabled anonymous authentication. The thing is: I didn't had to bother my self with IIS, since entire security part is done through WCF. So what I did is, enabled anonymous authentication. There is a good reason why you need that: without it, you cannot update client web reference. (at least I couldn't...). Ah, yes. That doesn't mean that anyone can use WCF service without logging in first... Second thing that confuses me, I was in the same domain, so every time when enabled anonymous authentication, I would get results from WCF service without any logging info that is provided in client proxy object of the class. The thing is, when I used different computer which is not in the domain, I couldn't get results as long as I don't provide Username, Password and Domain.

P.S. Last but not least, the part of code in client appliction: System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) => { return true; }; is from point of security, a true security risk. I added this code for testing, due to fact that other machine that is not in domain, Could not establish trust relationship for the SSL/TLS secure channel with authority 'server.domain'.

And finally, thank you all for help. Cheers.

Upvotes: 2

Related Questions