Reputation: 431
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 ''
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
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