I wanted to highlight a few options for securing and authenticating to RESTful services using WCF in some upcoming posts. This 1st one will speak to securing the pipeline that REST services communicate over which is HTTP.
As we all know (or if not, now you do!) you can secure the communication over HTTP by using a SSL certificate. This is the 1st step in securing the data being communicated from the client and our RESTful service. Let's begin by looking at our simple ServiceContract. It contains a GET method defined with a CLR name of 'GetCustomerData' being exposed as the noun 'Customer' to adhere to REST architecture standards:
namespace RESTfulSecuritySH
{
[ServiceContract]
public interface ISecureRESTSvcTest
{
[OperationContract]
[WebInvoke(Method = "GET",
ResponseFormat = WebMessageFormat.Json,
UriTemplate = "/Customer/{CustID}")]
Customer GetCustomerData(string CustID);
}
}
Behind the scenes the implemented code is returning the ID provided along a 'Customer' data contract with a some fake customer data. Obviously in a real implementation we would be connecting to a data repository to get this information, but for this example we will just hardcode values to be returned as security is the focus here and not the service implementation details.
namespace RESTfulSecuritySH
{
public class SecureRESTSvcTest : ISecureRESTSvcTest
{
public Customer GetCustomerData(string CustID)
{
Customer customer = new Customer()
{
ID = CustID,
FirstName = "John",
LastName = "Smithers",
PhoneNumber = "800-555-5555",
DOB = "01/01/70",
Email = "jsmithers@fancydomain.com",
AccountNumber = "1234567890"
};
return customer;
}
}
[DataContract]
public class Customer
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
public string PhoneNumber { get; set; }
[DataMember]
public string DOB { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public string ID { get; set; }
[DataMember]
public string AccountNumber { get; set; }
}
}
The default configuration for this service uses the webHttpBinding as required for RESTful services created using WCF. This configuration is for a self-hosted service as it provides a *baseAddress* value. This identical configuration could be used for an IIS hosted service. Just keep in mind the *baseAddress* will be ignored and replaced by the virtual directory structure set up in IIS.
<system.serviceModel>
<services>
<service behaviorConfiguration="SecureRESTSvcTestBehavior" name="RESTfulSecuritySH.SecureRESTSvcTest">
<host>
<baseAddresses>
<add baseAddress="http://DevMachine1234:8099/MyRESTServices/"/>
</baseAddresses>
</host>
<!--webHttpBinding allows exposing service methods in a RESTful manner-->
<endpoint address=""
binding="webHttpBinding"
behaviorConfiguration="webHttpBehavior"
contract="RESTfulSecuritySH.ISecureRESTSvcTest" />
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="SecureRESTSvcTestBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
<!--Required default endpoint behavior when using webHttpBinding-->
<endpointBehaviors>
<behavior name="webHttpBehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
At this point if we start our service using the WCF Test Client (if trying this locally), and then call the RESTful URI http://DevMachine1234:8099/MyRESTServices/Customer/8 to our service, you will get the JSON output (below) as expected (use Chrome, FF, or Safari to test. IE and Opera make you open a separate file). 'DevMachine1234' is the name of my dev box and eventually will be the name that needs to match up with the one provided to the SSL certificate which we will discuss in a moment.
{"AccountNumber":"1234567890","DOB":"01\/01\/70","Email":"jsmithers@fancydomain.com","FirstName":"John","ID":"8","LastName":"Smithers","PhoneNumber":"800-555-5555"}
OK, simple enough but notice there is data here that really is sensitive. The structure and design of our Customer data contract is not the focus of this post and is a flattened view of example customer data, but regardless some of it is sensitive. Let's now secure the transmission of this data over HTTPS using a SSL certificate.
For this example I am going to use a self-signed certificate I created locally and assign it on my machine to port 8099. I have (2) posts already that explain how to do this: Create A Self-Signed SSL Certificate Using IIS 7 and Applying and Using a SSL Certificate With A Self-Hosted WCF Service.
Let's focus on the configuration changes that had to be made. Now depending on how you decide to host your WCF service there may be subtle differences in the configuration. Also I should mention here that all of the configuration can be done in code if you chose to do so. I typically prefer configuration 1st, and only rely on using code to configure and run my service if there are elements that could be different at runtime and the configuration needs the flexibility of being dynamic. If this is not the case, I lean toward configuration because of its on-the-fly configurability and ease of reading. This is one case where either method works fine.
If using IIS to host your service, the certificate can be added through the IIS Management console and applied to the site instead of using the command line netsh.exe tool for self-hosted services. The 1st configuration change is to update the base address if you are using a self-hosted service to HTTPS:
<add baseAddress="https://DevMachine1234:8099/MyRESTServices/"/>
Next we must add a bindingConfiguration value to our endpoint as shown below:
<endpoint address=""
binding="webHttpBinding"
bindingConfiguration="webHttpTransportSecurity"
behaviorConfiguration="webHttpBehavior"
contract="RESTfulSecuritySH.ISecureRESTSvcTest" />
We must also add in this new binding configuration section. If you add a bindings section at the root of the system.serviceModel section, we can configure the security to be "Transport". When using Transport security with WCF, it automatically expects we want to use HTTPS for our endpoint. There are (3) different modes in the enumeration for the security element: None, Transport, and TransportCredentialOnly. 'None' indicates there is no required security on the endpoint, 'Transport' indicates we want to secure our endpoint with a SSL certificate using HTTPS, and 'TransportCredentialOnly' provides HTTP-based client authentication only and does not provide message integrity and confidentiality. The new endpointConfiguration is below:
<bindings>
<webHttpBinding>
<binding name="webHttpTransportSecurity">
<security mode="Transport" />
</binding>
</webHttpBinding>
</bindings>
Next within the service behavior we must enable HTTPS and disable HTTP. This way our RESTful service can only be accessed via HTTPS. This is a very important step if you intend to have your service only accessed in a secure manner. Otherwise you leave the door open to still access it via HTTP. This may be an OK scenario if hosting non-secure data that you want to be able to be accessed via HTTP or HTTPS, but otherwise it should only be one or the other.
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>
Lastly, we need to update the metadata publishing endpoint to use HTTPS as well:
<endpoint address="mex"
binding="mexHttpsBinding"
contract="IMetadataExchange" />
At this point restart your service. If you encounter any errors, it is probably because the SSL certificate does not match the name of your service, the SSL cert has not been applied correctly to the port (self-hosted services only), or the all the configuration updates were not made correctly. If you completed everything successfully, you can now access your site at the following URL: https://DevMachine1234:8099/MyRESTServices/Customer/8
Now try opening a new browser and access it via HTTP; it will not work and this is what we wanted. We have now secured our RESTful service's communication.
One last big ticket item here that will set me up for my next posts on securing RESTful services has to do with the security context before and after securing our service. This will ultimately allow us to authenticate our users and move forward with security. Either debugging the RESTful call to your existing 'GetCustomer' method or from a new call that simply returns a string, try looking at the following lines of code:
//Look at the ServiceSecurityContext both using secured and non-secured service configurations:
ServiceSecurityContext svcSecurityContext;
svcSecurityContext = OperationContext.Current.ServiceSecurityContext;
Notice how if you run your service without Transport security using HTTPS, the ServiceSecurityContext instance will be 'null'. However once we secure our service, this instance will have value. Specifically if you look at the .IsAnonymous property, you will notice it is now = "true" upon securing our service.
We will work on populating this value in my next post and moving forward to authenticating the client.
0 comments:
Post a Comment