This is the continuing section on the subject matter. For review, refer to WebAPI Thoughts - Introduction article.
Table of Contents
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.
-
- 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.
-
- 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.
-
- 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:
- 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.
There are different ways of validating client requests before they are processed in the server side. To mention some:
- Validating certificate using DelegateHandler
- Validating HTTPS request URL using AuthorizationFilter
-
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.
public class CertificateValidationHandler : DelegatingHandler
{
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);
}
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> {
StoreLocation.CurrentUser,
StoreLocation.LocalMachine
};
bool? verified = null;
OpenFlags openFlags = OpenFlags.OpenExistingOnly |
OpenFlags.ReadOnly;
List<string> thumbPrintCollection = new List<string>();
if (thumbPrints.FirstOrDefault().Contains(',')) {
thumbPrintCollection.AddRange(thumbPrints.FirstOrDefault().Split(','));
}
else
{
thumbPrintCollection.Add(thumbPrints.FirstOrDefault());
}
}
catch (Exception exception)
{
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)
{
config.MessageHandlers.Add(new CertificateValidationHandler());
}
}
-
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:
public class HttpsValidator : AuthorizationFilterAttribute
{
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)
{
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] public class PhysicianController : PhysicianBaseController
{
}
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.
-
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.
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);
var cert = new X509Certificate2(File.ReadAllBytes(@"c:\MyCert.cer"));
List<string> certs = new List<string>();
certs.Add(cert.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);
}
}
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);
}
}
-
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
public class PhysicianSelfHostController : PhysicianBaseController
{
[AcceptVerbs("Post","Put")]
public HttpResponseMessage CalculateSalaryRaise(HttpRequestMessage request)
{
try
{
InternalPhysician physicianBase =
request.Content.ReadAsAsync<InternalPhysician>().Result;
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
.
private async static Task CallSelfHostHttpsService()
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine
("Start processing Self-Host Https service operation call");
try
{
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;
using (HttpSelfHostServer server = new HttpSelfHostServer(config))
{
server.OpenAsync().Wait();
Console.WriteLine(string.Concat("Opening server ", serverDefaultSecureURL));
Console.WriteLine();
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);
}
}
private static async Task ProcessRaiseSalaryRequest(Uri address)
{
using (HttpClient httpClient = new HttpClient())
{
HttpRequestMessage request = new HttpRequestMessage();
request.Method = HttpMethod.Post; 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));
}
});
}
}
-
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
public class ServerStartup
{
public void Configuration(IAppBuilder appBuilder)
{
HttpListener listener =
(HttpListener)appBuilder.Properties["System.Net.HttpListener"];
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.
private async static Task CallOWINHttpsService()
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Start processing OWIN Https service operation call");
try
{
using (WebApp.Start<ServerStartup>(serverDefaultSecureURL.AbsoluteUri))
{
Console.WriteLine(string.Concat("Opening server ", serverDefaultSecureURL));
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