Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Web API Thoughts 2 of 3 - Working with HTTPS

0.00/5 (No votes)
24 Nov 2014 2  
ASP.NET Web API related projects. It touches most parts of the technology through the articles on Data Streaming, Working with HTTPS and Extending Web API Documentation.

This is the continuing section on the subject matter. For review, refer to WebAPI Thoughts - Introduction article.

Table of Contents

Working with HTTPS

HTTPS/SSL (Hypertext Transfer Protocol Secure/Secure Socket Layer) is a protocol that manages a secure transmission of message/resources on computer network such as internet. A site that uses this protocol can be easily identified by looking the site URL. For example, browsing an HTTPS enabled site in Google Chrome shows a locked-key-like icon followed by "https" and the rest of site URL. See the picture below.

Chrome Browser
Verified Https Unverified Https

So What Things Can Be Observed From These Pictures?

  • Certificate - Technically known as digital certificate is a digitally signed statement that binds the value of a public key to the identity of the person, device, or service that holds the corresponding private key.
  • Certificate Authority (CA) - is a trusted entity that issues digital certificate. From the picture above, a VeriSign is CA that verifies the URL as a trusted URL.

So What Happens If You Accessed an HTTPS Enabled Website?

  • You trusted a browser to use an existing trusted CA.
  • You trusted the CA to verify trusted sites.
  • The site contains a verifiable certificate.
  • The CA identifies and verifies the certificate of the site. If successful, the browser address bar indicates a green key icon with https site URL. If not, the browser address bar shows a red key icon with stricken https site URL. The failed one will also displays a warning/error content saying "This is probably not the site you are looking for!" or "There is a problem with this website's security certificate". The message content varies depending on the browser.
  • Finally, you allowed the browser to access the site.

Note: There are lot of things processed behind the verification and authentication of certificate which will not be covered by this article.

Participating Projects

  • WebAPISecureSocketLayering
  • WebAPIClient
  • WebAPICommonLibrary
  • POCOLibrary
  • Assets

So how is this protocol can be used in Web API service? The framework has plentiful ways to use the protocol by defining, validating and consuming the service. The various ways and steps will be explained shortly.

  1. Enabling HTTPS on Web API service deployed in IIS(High Level steps)

    • Get SSL certificate
    • Install the certificate on the server where the service resides
    • Create an HTTPS binding on a service
      • Binding a Certificate to the service
      • Select Default Web Site and choose Edit site, Bindings... Actions pane
      • Click "Add"
      • Type: https
      • IP address: "All Unassigned"
      • Port: 443
      • SSL certificate: [you certificate name]
      • Click "OK"
      • Click "Close"
    • Browse the site to make sure you can communicate through HTTPS

    Detail can be found in the reference section.

  2. Creating Self-signed certificate on IIS 7.5 and Binding to HTTPS Type to Web API Service

    • Open IIS
    • Select Sites folder
    • Select Default Web Site
    • In Features view, double-click Server Certificates
    • In the Actions pane, click Create Self-Signed Certificate
    • On the Create Self-Signed Certificate page, type a [friendly name] for the certificate in the Specify a friendly name for the certificate box, and then click OK.
    • This will create a certificate and add it under Trusted Root Certification Authorities. If not, follow the demonstrative pictures under Assets/Trusted Root Certification Authorities folder.
    • Bind the certificate to the desired Web API service. For binding, See the previous sub section, Binding Certificate to a service.

    Tip: A step by step demonstrative pictures are available under Assets/Creating Self-signed Certificate And Binding to Service or Web site folder.

  3. Enabling SSL on Web API project using IIS Express

    • Select your Web API project
    • Go to Properties window

    • Setting SSL Enabled property to True will generate an SSL URL. Look at the picture below:

Exporting Certificate

  • Open Certificate Manager tool (Certmgr.msc). Start -> Run -> Certmgr.msc
  • Follow the steps shown in the pictures under Assets/Exporting Certificate folder.

Note: While exporting certificate, make sure not to include the private key.

Validating HTTPS Request URL and Certificate

There are different ways of validating client requests before they are processed in the server side. To mention some:

  1. Validating certificate using DelegateHandler
  2. Validating HTTPS request URL using AuthorizationFilter
    1. Validating Certificate using DelegateHandler

      A Web API service request can be validated based on a certificate sent from the client. If it does not succeed, then the service notifies the client with appropriate message. An Http delegate handler will be used to validate the certificate prior to executing the request method. The validation performs a check on the certificate thumbprint that is sent along with the request. Once a certificate thumbprint is presented, it will be verified as a valid certificate. If it succeeds, it will allow the requested service method to be executed.
      /// <summary>
      /// Certificate validator handler class
      /// </summary>
      public class CertificateValidationHandler : DelegatingHandler
      {
          /// <summary>
          /// Send request asynchronously
          /// </summary>
          /// </param name="request">HttpRequestMessage value</param>
          /// </param name="cancellationToken">CancellationToken value</param>
          /// <returns>HttpResponseMessage object</returns>
          protected override System.Threading.Tasks.Task<HttpResponseMessage>
                  SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
          {
              HttpResponseMessage response = ValidateCertificate(request);
              if (response.StatusCode == HttpStatusCode.OK)
                  return base.SendAsync(request, cancellationToken);
              else
                  return Task<HttpResponseMessage>.Factory.StartNew(() => response);
          }
          
         /// <summary>
         /// Validate Certificate
         /// </summary>
         /// </param name="request">HttpRequestMessage value</param>
         /// <returns>HttpResponseMessage object</returns>
         private HttpResponseMessage ValidateCertificate(HttpRequestMessage request)
         {
              IEnumerable<string> thumbPrints;
      
              if (!request.Headers.TryGetValues("Thumbprint", out thumbPrints) ||
                  thumbPrints == null ||
                  (thumbPrints != null && thumbPrints.Count() == 0))
              {
                  return request.CreateResponse(HttpStatusCode.NotAcceptable, new Message() 
                  { Content = "Thumbprint request header is not available !" });
              }
              try
              {
                  List<StoreLocation> locations = 
                          new List<StoreLocation> // Certificate location indicators
                  {    
                      StoreLocation.CurrentUser, 
                      StoreLocation.LocalMachine
                  };
      
                  bool? verified = null; // A flag used to check Certificate validation
      
                  OpenFlags openFlags = OpenFlags.OpenExistingOnly | 
                                        OpenFlags.ReadOnly;
      
                  List<string> thumbPrintCollection = new List<string>();
      
                  if (thumbPrints.FirstOrDefault().Contains(',')) // Has many thumbprints
                  {
                      thumbPrintCollection.AddRange(thumbPrints.FirstOrDefault().Split(','));
                  }
                  else
                  {
                      thumbPrintCollection.Add(thumbPrints.FirstOrDefault());
                  }
      
                  // .........................................
                  // Full code available in the source control
                  // .........................................            
              }
              catch (Exception exception)
              {
                  // Log error
                  return request.CreateResponse(HttpStatusCode.BadRequest, new Message() 
                  { Content = string.Concat
                  ("Exception happens while processing certificate ! \n", exception) });
              }
         }
      } 

      Once the certificate delegate is created, it will be registered under http configuration as shown below:

       public static class WebApiConfig
       {
          public static void Register(HttpConfiguration config)
          {
              // .........................................
              // Full code available in the source control
              // .........................................
              config.MessageHandlers.Add(new CertificateValidationHandler());
          }
       } 
    2. Validating HTTPS Request URL using AuthorizationFilter

      The other validation is to enforce any incoming request to be transferred through only HTTPS protocol. This can be achieved by using authorization filter attribute for the entire service methods or for a particular method exposed inside the service. The following code shows how to do so:

      /// <summary>
      /// Https URI validator class
      /// </summary>
      public class HttpsValidator : AuthorizationFilterAttribute
      {
          ///  <summary>
          ///  Validate request URI
          ///  </summary>
          ///  <param name="actionContext">HttpActionContext value</param>
          public override void OnAuthorization(HttpActionContext actionContext)
          {
              if (actionContext != null && actionContext.Request != null && 
              !actionContext.Request.RequestUri.Scheme.Equals(Uri.UriSchemeHttps))
              {
                  var controllerFilters = actionContext.ControllerContext.ControllerDescriptor.GetFilters();
                  var actionFilters = actionContext.ActionDescriptor.GetFilters();
      
                  if ((controllerFilters != null && controllerFilters.Select
                  (t => t.GetType() == typeof(HttpsValidator)).Count() > 0) ||
                      (actionFilters != null && actionFilters.Select(t => 
                      t.GetType() == typeof(HttpsValidator)).Count() > 0))
                  {
                          actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden, 
                              new Message() { Content = "Requested URI requires HTTPS" }, 
                              new MediaTypeHeaderValue("text/json"));
                  }
              }
          }
      } 

      Once Https authorization filter is created, similar to certificate delegate, it will be registered under http configuration as shown below:

      public static class WebApiConfig
      {
          public static void Register(HttpConfiguration config)
          {
              // .........................................
              // Full code available in the source control
              // .........................................
              config.Filters.Add(new HttpsValidator());
          }
      } 

      Once the configuration is done, it will validate the entire Web API controller or a specific method as shown below:

      [HttpsValidator] // Enforce HTTPS request to the controller
      public class PhysicianController : PhysicianBaseController
      {              
          // .........................................
          // Full code available in the source control
          // .........................................
      } 

How To Use in Client Application

So far, the Web API service is prepared to use HTTPS protocol. Let's see how to consume in client applications. There are several ways to consume an Https enabled Web API service. To demonstrate, I will use a Self-Signed Https enabled service hosted on IIS and a Self-Hosted Https enabled Web API service using ASP.NET Self-Host and OWIN.

  1. Consuming an HTTPS Enabled Web API Service Hosted on IIS

    As I discussed earlier, the Web API service requires a certificate thumbprint to be passed as part of a request. In order to send a request, the client must have a certificate file or a thumbprint value of a certificate. Once the certificate thumbprint is available, it will be attached to an http request header.

    Tip: If certificate is not available, see Export Certificate section.

    Here is the client code that sends a request using a certificate.

    /// <summary>
    /// Call IIS Https Service
    /// </summary>
    /// <returns>Awaitable task object</returns>
    private async static Task CallIISHttpsService()
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine("Start processing IIS Https service operation call");
    
        string actionURI = "physician/activephysicians";
    
        try
        {
            string uri = string.Concat(secureURL.AbsoluteUri, actionURI);
    
            Uri address = new Uri(uri);
    
            HttpRequestMessage request = new HttpRequestMessage
                    (HttpMethod.Get, address); // HttpMethod.Post is also works
    
            var cert = new X509Certificate2(File.ReadAllBytes(@"c:\MyCert.cer"));
            //var cert2 = new X509Certificate2(File.ReadAllBytes(@"c:\MyCert2.cer"));
    
            List<string> certs = new List<string>();
            certs.Add(cert.Thumbprint);
            //certs.Add(cert2.Thumbprint);
            request.Headers.Add("Thumbprint", certs);
    
            CancellationToken token = new CancellationToken();
    
            using (HttpClient httpClient = new HttpClient())
            {
                await httpClient.SendAsync(request, token).
                    ContinueWith((response)
                        =>
                        {
                            try
                            {
                                ProcessIISHttpsResponse(response);
                            }
                            catch (AggregateException aggregateException)
                            {
                                Console.ForegroundColor = ConsoleColor.Red;
                                Console.WriteLine
                                (string.Format("Exception {0}", aggregateException));
                            }
                        });
            }
        }
        catch (Exception exception)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(exception);
        }
    }
    
    /// <summary>
    /// Process IIS Https response object
    /// </summary>
    /// <param name="response">
    /// Awaitable HttpResponseMessage task value</param>
    private static void ProcessIISHttpsResponse(Task<HttpResponseMessage> response)
    {
        if (response.Result.StatusCode == HttpStatusCode.OK ||
            response.Result.StatusCode == HttpStatusCode.Created)
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine(string.Concat("Response message: \n",
                JsonConvert.SerializeObject
                (response.Result.Content.ReadAsAsync<List<PhysicianBase>>().TryResult(), 
                Formatting.Indented)));
        }
        else
        {
            ProcessFailResponse(response);
        }
    } 
  2. Consuming an HTTPS Enabled Self-Hosted Web API Service

    ASP.NET Web API Self-Host allows consuming a Web API service without installing IIS. Once you define Web API controller, it will be hosted and consumed by using ASP.NET Self host libraries in client application. To work with the library:
    • Install the library to the client application
       PM > Install-Package Microsoft.AspNet.WebApi.SelfHost
    • Define a Web API controller named PhysicianSelfHostController which has a method CalculateSalaryRaise
      /// <summary>
      /// Physician api controller class
      /// </summary>
      public class PhysicianSelfHostController : PhysicianBaseController
      {
          /// <summary>
          /// Calculate Salary raise
          /// </summary>
          /// <param name="request">HttpRequestMessage value</param>
          /// <returns>HttpResponseMessage object</returns>
          [AcceptVerbs("Post","Put")]
          public HttpResponseMessage CalculateSalaryRaise(HttpRequestMessage request)
          {
              try
              {
                  InternalPhysician physicianBase = 
                      request.Content.ReadAsAsync<InternalPhysician>().Result;
                  // Calculate fixed salary raise
                  physicianBase.Salary = physicianBase.CalculateSalaryRaise();
                  return Request.CreateResponse(HttpStatusCode.OK, 
                  physicianBase, new MediaTypeHeaderValue("text/json"));
              }
              catch (Exception exception)
              {
                  return Request.CreateErrorResponse
                      (HttpStatusCode.InternalServerError, exception);
              }
          }
      } 

      The client code will register the self host https address and tries to communicate the Web API service resource. Since we are communicating through Https, a certification validation should be performed before reaching the requested resource. To do so, we use ServicePointManager class to validate the certificate by calling ServerCertificateValidationCallback which invokes a method callback that returns bool.

      /// <summary>
      /// Call SelfHost Https Service
      /// </summary>
      /// <returns>Awaitable task object</returns>
      private async static Task CallSelfHostHttpsService()
      {
          Console.ForegroundColor = ConsoleColor.Green;
          Console.WriteLine
              ("Start processing Self-Host Https service operation call");
      
          try
          {
              // Set up SelfHost configuration
              HttpSelfHostConfiguration config = 
                  new HttpSelfHostConfiguration(serverDefaultSecureURL);
      
              config.MapHttpAttributeRoutes();
      
              config.Routes.MapHttpRoute(
                  name: "SelfHostActionApi",
                  routeTemplate: "{controller}/{action}/{id}",
                  defaults: new { id = RouteParameter.Optional }
              );
      
              config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = 
                            TypeNameHandling.Auto;
      
              // Create server
              using (HttpSelfHostServer server = new HttpSelfHostServer(config))
              {
                  // Open sever
                  server.OpenAsync().Wait();
                  Console.WriteLine(string.Concat("Opening server ", serverDefaultSecureURL));
                  Console.WriteLine();
      
                  // In order to communicate with server, 
                  // need to validate remote server certificate 
                  ServicePointManager.ServerCertificateValidationCallback +=
                      new RemoteCertificateValidationCallback(ValidateRemoteCertificate);
      
                  Uri address = new Uri
                  (serverDefaultSecureURL, "physicianselfhost/calculatesalaryraise");
                  await ProcessRaiseSalaryRequest(address);
      
                  server.CloseAsync().Wait();
              }
          }
          catch (Exception exception)
          {
              Console.ForegroundColor = ConsoleColor.Red;
              Console.WriteLine(exception);
          }
      }
      
      /// <summary>
      /// Process Raise Salary request object
      /// <param name="address">Request Uri value</param>
      /// </summary>
      /// <param name="response">Awaitable task object</param>
      private static async Task ProcessRaiseSalaryRequest(Uri address)
      {
          using (HttpClient httpClient = new HttpClient())
          {
              HttpRequestMessage request = new HttpRequestMessage();
              request.Method = HttpMethod.Post; // HttpMethod.Put is also works
              request.RequestUri = address;
      
              PhysicianBase physician = new InternalPhysician()
              {
                  FirstName = "Joe",
                  LastName = "Doe",
                  Salary = 120000
              };
      
              string requestobject = JsonConvert.SerializeObject(physician, Formatting.Indented);
              Console.ForegroundColor = ConsoleColor.Green;
              Console.WriteLine("Before Salary raised \n{0}", requestobject);
      
              request.Content = new ObjectContent(typeof(PhysicianBase), physician, 
              new JsonMediaTypeFormatter(), new MediaTypeHeaderValue("text/json"));
      
              await httpClient.SendAsync(request).
                  ContinueWith((response)
                  =>
                  {
                      try
                      {
                          ProcessRaiseSalaryResponse(response);
                      }
                      catch (AggregateException aggregateException)
                      {
                          Console.ForegroundColor = ConsoleColor.Red;
                          Console.WriteLine(string.Format
                              ("Exception {0}", aggregateException));
                      }
                  });
          }
      } 
  3. Consuming an HTTPS Enabled Web API Service using OWIN

    OWIN (Open Web Interface for .NET) is an interface that helps to separate the server and the applications. Similar to Self-Host, IIS doesn’t need to be installed to consume the service. To work with the library:
    • Install the library to the client application
       PM > Install-Package Install-Package Microsoft.AspNet.WebApi.Owin
    • First let’s define a ServerStartup class that helps to configure the server
      /// <summary>
      /// OWIN Server StartUp class
      /// <summary>
      public class ServerStartup
      {
          public void Configuration(IAppBuilder appBuilder)
          {
              HttpListener listener = 
              (HttpListener)appBuilder.Properties["System.Net.HttpListener"];
      
              // For Https set as an Anonymous 
              listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
      
              HttpConfiguration config = new HttpConfiguration();
      
              config.MapHttpAttributeRoutes();
      
              config.Routes.MapHttpRoute(
                  name: "OWINActionApi",
                  routeTemplate: "{controller}/{action}/{id}",
                  defaults: new { id = RouteParameter.Optional }
              );
      
              config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = 
                                  TypeNameHandling.Auto;
              appBuilder.UseWebApi(config);
          }
      } 
    • Use previously defined ApiController, PhysicianSelfHostController
    • Define a method to communicate to the server.
      /// <summary>
      /// Call OWIN Https Service
      /// </summary>
      /// <returns>Awaitable task object</returns>
      private async static Task CallOWINHttpsService()
      {
          Console.ForegroundColor = ConsoleColor.Green;
          Console.WriteLine("Start processing OWIN Https service operation call");
      
          try
          {
              // Create server
              using (WebApp.Start<ServerStartup>(serverDefaultSecureURL.AbsoluteUri))
              {
                  Console.WriteLine(string.Concat("Opening server ", serverDefaultSecureURL));
      
                  //In order to communicate with server,need to validate remote server certificate 
                  ServicePointManager.ServerCertificateValidationCallback +=
                      new RemoteCertificateValidationCallback(ValidateRemoteCertificate);
      
                  Uri address = new Uri(serverDefaultSecureURL, 
                      "physicianselfhost/calculatesalaryraise");
                  await ProcessRaiseSalaryRequest(address);
              }
          }
          catch (Exception exception)
          {
              Console.ForegroundColor = ConsoleColor.Red;
              Console.WriteLine(exception);
          }
      } 

What's Next

The next section explains about Extending Web API Documentation

References

History and GitHub Version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here