Thursday, October 28, 2010

Webfarm and Servername

If your WCf service is hosted with a commercial hosting provider which may or many not be using a web farm, you may find that when you browse to your service that it shows a different address for the svcutil.exe than the dns address and when you download the wsdl/svcutil that this wsdl/svcutil is referencing the internal server name instead of your domain name.

For example, if you purchased a domain name called aliayman.com and your hosting company is storing and hosting your files on an internal server called HostingCompanyServer01. Now let's imagine you created a web service called MyService.svc.

Now when you browse to http://aliayman.com/MyService.svc

you will see your service information but you will notice the line that contain the svcutil.exe is telling you to get the wsdl from

svcutil.exe http://HostingCompanyServer01/Myservice.svc

this is a problem because you can not access this server name, you can access aliayman.com but you can not access this specific host name.

Also, when you try to reference this service in your code you will find that your reference is pointing to HostingCompanyServer01 instead of aliayman.com

You have to solutions to this problem.

Solution 1: after you create the reference search your code for HostingCompanyServer01 and change it to aliayman.com and at least your program will be able to deal with the web service. You will find HostingCompanyServer01 in *.wsdl and *.svcinfo files.

Solution 2: which is the best solution is to change the binding of the IIS installed on HostingCompanyServer01. This is off course if you have access to this IIS. to view the binding find your site identifier number (let's assume it is 1) and issue these commands to view your binding. (notice your site identifier 1 is used before ServerBinding and SecureBinding below)

cscript.exe //nologo %systemdrive%\inetpub\adminscripts\adsutil.vbs get W3SVC/1/ServerBindings

cscript.exe //nologo %systemdrive%\inetpub\adminscripts\adsutil.vbs get W3SVC/1/SecureBindings

then issue these commands to correct the binding

cscript.exe //nologo %systemdrive%\inetpub\adminscripts\adsutil.vbs set W3SVC/1/ServerBindings "80:aliayman.com"

cscript.exe //nologo %systemdrive%\inetpub\adminscripts\adsutil.vbs set W3SVC/1/SecureBindings "443:aliayman.com"

Then restart your application pool

I found these advices about the binding in this blog post

Wednesday, October 13, 2010

WCF over Https and User Name/Password

Web services are mandatory nowadays and for a long time security has been a tough thing to do. Now with WCF you can just configure wsHttpBinding and you're done with a great deal of security. This binding will encrypt the communication between the web service and the client, however the client should be able to do the same encryption and decryption that the service understands. Hence, your client should be a .Net client which may not always the case. You may have a Java Client which will not be able to perform wsHttpBinding, or you may have a .Net client but you want to send Ajax requests to your WCF service which again Javascript will not be able to perform wsHttpBinding because you need a .Net to do it and .Net is not available to Javascript. At the end of the day you may find that wsHttpBinding is not that great if you are not totally in a Microsoft world.

In order to have your web service available to the entire world, including Ajax and Java then you will need to make your service available under https protocol. This way you guarantee the entire world can talk to your service securely and the parameters you are sending to the web service operations are encrypted by the client automatically and decrypted by the service. Also, the service response is encrypted for you automatically using the standards public/private key strong encryption and decrypted by the client automatically.

In order to enable SSL for your WCF follow the steps outlined in this article.

1- Get SSL Certificate: You will need to purchase and configure an SSL certificate for your domain name. You can buy it from VeriSign starting from $99 or you can generate your own by installing Windows 2003 Certificate Authority then request a certificate for your machine and issue it from that certificate authority. If you use your own certificate issued by certificate authority you will encounter a trust issue and you may need to write one line of code to avoid this trust issue. If you encounter this trust issue you will get the message "The remote certificate is invalid according to the validation procedure", you can avoid this message by writing this line of code

ServicePointManager.ServerCertificateValidationCallback = delegate(object sender, X509Certificate certificate, X509Chain chain,SslPolicyErrors sslPolicyErrors) { return true; };
2- Bind IIS: You will need to make sure that your IIS is bound to your domain name. usually when you host a WCF on an IIS server IIS will always recognize the local server name and does not recognize the domain name. In order to learn about this issue and find out what to do read this article here But in a nutshell, you will need to issue two command
cscript.exe //nologo %systemdrive%\inetpub\adminscripts\adsutil.vbs set W3SVC/1/ServerBindings ":80:Your_Domain_Name"

cscript.exe //nologo %systemdrive%\inetpub\adminscripts\adsutil.vbs set W3SVC/1/SecureBindings ":443:Your_Domain_Name"

3- Enable Secure Metadata: Then for the service metadata set httpsGetEnabled="True" in your service config more info.
<>

Check listing 7 here

4- Set Binding Security Mode: Define binding properties for basicHttpBinding as per Listing 8 here

5- Attach Binding configuration to EndPoint: Add this binding configuration to the service end point as per listing 9 here

6- Set Mex endpoint to mexHttpsBinding: As per listing 9 here

7- Set the baseAddress: You can set the base address optionally as per listing 9 here

You WCF Service is now secure and you can browse to it as follows.

When you try to call your service you should see the following in fiddler. one connection on port 443 and the entire communication is encrypted.


8- Require Secure Channel: Now, using IIS go to the Default web site or the site that has your service and go to Directory Security, Secure Communication, Edit and click Require Secure Channel (SSL). Now your service is not available under http. It is only available under https here is how to change the setting


Now if you try to browse to the service using http you will get the error "The page must be viewed over a secure channel as per the image below


9- Automatically redirect http to https: If you want to redirect http to https you can read the information in this post or this one.

Additional References for SSL


For the user name and password: You will need to do the following

1- Send credentials at the client: In your client code make sure to send the credentials. If your client is a .Net client you can send the credentials as follows

var svc = new MySecureService.MySecureServiceClient();
var cred = new NetworkCredential("MyUserName","MyPassword");
svc.ClientCredentials = cred;
var result = svc.Operation1();

2- Implement IHttpModule: Create a class and implement System.Web.IHttpModule, you can use the following code

public class UserAuthentication : IHttpModule
{
public void Init(HttpApplication context)
{
context.AuthenticateRequest += new EventHandler(this.OnAuthenticateRequest);
context.EndRequest += new EventHandler(this.OnEndRequest);
}

public void OnAuthenticateRequest(object sender, EventArgs e)
{
var application = (HttpApplication)sender;
var header = application.Request.Headers["Authorization"];

try
{
if (!string.IsNullOrEmpty(header))
{

header = header.Trim();
if (header.IndexOf("Basic", 0) != 0)
{
throw new Exception("Access Denied.");
}

var encodedCredentials = header.Substring(6);
var decodedBytes = Convert.FromBase64String(encodedCredentials);
var s = new ASCIIEncoding().GetString(decodedBytes);
var credentials = s.Split(new char[] { ':' });
var username = credentials[0];
var password = credentials[1];

if (!ValidateUser(username, password))
{
throw new Exception("Access Denied.");
}
}
else
{
throw new Exception("Access Denied.");
}
}
catch (Exception ex)
{
application.Response.StatusCode = 401;
application.Response.StatusDescription = ex.Message;
application.Response.Write("" + ex.Message + "");
application.Response.End();
}
}

public void OnEndRequest(object sender, EventArgs e)
{
if (HttpContext.Current.Response.StatusCode == 401)
{
var context = HttpContext.Current;
context.Response.StatusCode = 401;
context.Response.AddHeader("WWW-Authenticate", "Basic Realm");
}
}

public void Dispose()
{

}

private bool ValidateUser(string username, string password)
{

// Insert Custom User Validation here

var validUsername = "MyUserName";
var validPassword = "MyPassword";

if (username == validUsername &&
password == validPassword)
{
return true;
}
else
{
return false;
}
}
}

3- Add Module to your web.config: In the modules section add this module in your web.config as follows

< name="UserAuthentication" type="MyNameSpace.UserAuthentication">