Thursday, May 24, 2012

Creating a WCF RESTful Service And Secure It Using HTTPS Over SSL

Well I have had a few posts now on security, and focused some specifically on HTTPS and WCF. One of the most popular types of services we can create using WCF are RESTful services. The REST architecture and RESTful services have become so popular due to their inherent flexibility and being more consumer and platform agnostic as opposed to their WCF SOAP WS-* counterparts. However with this popularity and quickly expanding use we must not forget about securing our RESTful services especially when exposing them for wide use on the internet. Since REST services communicate over HTTP, we can leverage our existing knowledge and security principals we use for traditional websites that communicate over HTTP.

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.

22 comments:

  1. Nice and Needed Post Waiting For Next Post

    ReplyDelete
  2. Thanks for shared this information and I need this post.

    ReplyDelete
  3. Hi Allen,

    I have made the service which is http based and works fine. I want to make it SSL-HTTPS enabled.

    I made all the above changes to make the service HTTPS-SSL enabled.

    But I start receiving :- Could not find a base address that matches scheme https for the endpoint with binding WebHttpBinding. Registered base address schemes are [http].

    The moment I remove, bindingConfiguration="webHttpTransportSecurity" from service endpoint. the above error goes away and gives new one:-

    Could not find a base address that matches scheme https for the endpoint with binding MetadataExchangeHttpsBinding. Registered base address schemes are [http].

    This error goes away if I remove the "s" from mexhttpsbinding.

    which then gives:-
    The HttpsGetEnabled property of ServiceMetadataBehavior is set to true and the HttpsGetUrl property is a relative address, but there is no https base address. Either supply an https base address or set HttpsGetUrl to an absolute address.

    it goes away if I remove httpsGetEnabled="true"

    Thus, after removing all changes, I am not able to achieve the HTTPS-SSL enabled service.

    It keeps on saying that registered base address scheme is http and not https.

    Any idea what must be the reason. However, I was successfully able to apply the certificate.

    Please see the config settings:-











































































    ReplyDelete
  4. Thanks for the nice post sir. It really saved my time a lot.

    ReplyDelete
  5. Great blog man, thank you so much.

    ReplyDelete
  6. Great post, followed it and it was tremendously helpful. Would suggest if you are not familiar with certificate creation to post a link to that. This was my first time to do so and I had to do some research...

    ReplyDelete
  7. You saved me so many hours of work. I was getting frustrated on how to achieve this. Thanks a ton.

    ReplyDelete
  8. Thank you. A developers butt has been saved.

    ReplyDelete
  9. Great! Saving butts is a good thing :D Glad it helped and thanks for the feedback.

    ReplyDelete
  10. Thanks, Allen! Great blog. I'm new to WCF and this really helped me out.

    ReplyDelete
  11. Hi,

    I have hosted my service on https and used the above configutation. Now the service returns 404 not found.

    On removing the following line from binding configuration the Get services works but Post services do not:



    Can you please explain why it shows 404 error.

    Thanks
    Kashif

    ReplyDelete
  12. Allen this worked flawlessly for me - thanks for putting this up!

    ReplyDelete
  13. This was exactly what I needed. After searching around and trying configuration after configuration and getting nowhere, this was the tutorial that worked flawlessly. Thank you very much for spending your time helping the rest of us.

    ReplyDelete
  14. thank you very much alley... it is very useful to me...

    ReplyDelete
  15. Very useful and still working nowadays.... cheers!

    ReplyDelete
  16. Thanks a ton! That was really what I needed! Wonderful website, - lots of useful information here. I’m sending it to some friends of mine.

    ReplyDelete
  17. Hi .Thank you for your great post.it is so useful;in fact i have a problem about 2 weeks and i can't find any solutions for it ,could you please take a look and give me your comment .

    https://stackoverflow.com/questions/45767892/customauthorizationpolicy-evaluate-method-never-fires-in-wcf-webhttpbinding

    ReplyDelete
  18. Thank you for your post. It's useful. The above request working in WcfTestClient, but when I used Postman it's failing "SSL Error: Self signed certificate". I have added certificate in setting --> certificate --> Client Certificate in Postman and also enabled ssl verification. In App.config of Service I have mentioned

    . Could you please share your thought on this

    ReplyDelete