Implementing CORS support in WCF

The code for this post can be downloaded from the MSDN Code Gallery.

A pair of popular posts which I did a couple months back was to show how one can implement CORS (Cross-Origin Resource Sharing) in the net ASP.NET Web API framework. This week I found a couple of posts in the WCF forums from a user who wanted to make cross-domain calls to a WCF REST service. They were trying to use JSONP, but it didn’t work because the request needed to be made using non-GET verbs. So let’s try to implement the same support which we did fairly easily in the new API in WCF.

Cross-domain calls

A quick recap about the problem: in order to prevent malicious sites to “stealing” cookies from good sites and using them to get access to protected resources (imagine going to a bad site, and scripts in that site accessing your online bank and transferring your money elsewhere), browsers block by default AJAX requests going to domains other than the one where the HTML page originated. This is good for security purposes, but it blocks some valid scenarios, such as mash-up applications which gather data from many sources. There are some alternatives to make this scenario work, including JSONP (JSON with Padding), or using a separate “proxy” service on the same domain as the page to route the requests to the destination server. But those approaches have limitations: JSONP only works for GET requests (since it uses the <script> element in the HTML DOM, and proxy services need to be deployed on many places and add another level of indirection (and point of failure) to the system.

CORS (Cross-Origin Resource Sharing) is a new specification which defines a set of headers which can be exchanged between the client and the server which allow the server to relax the cross-domain restrictions for all HTTP verbs, not only GET. Also, since CORS is implemented in the same XmlHttpRequest as “normal” AJAX calls (in Firefox 3.5 and above, Safari 4 and above, Chrome 3 and above, IE 10 and above – in IE8/9, the code needs to use the XDomainRequest object instead), the JavaScript code doesn’t need to worry about “un-padding” responses or adding dummy functions. The error handling is also improved with CORS, since services can use the full range of the HTTP response codes (instead of 200, which is required by JSONP) and the code also has access to the full response instead of only its body.

CORS operation

There are two types of requests in the CORS world, “normal” requests and preflight requests. Normal requests are the requests which the page would normally make to the service, with an additional header, “Origin”, which indicates the origin and the service can determine whether to allow cross-domain calls from that origin or not (via the “Access-Control-Allow-Origin” response header). “Safe” requests (GET and HEAD) only use that extra headers to work. The browser will add the Origin header to requests going to domains other than the one where the page originated, and if the service doesn’t allow that domain, then the call will fail.

“Unsafe” requests, such as POST, PUT or DELETE, can’t be done the same way. If the service isn’t CORS-aware, it would ignore the “Origin” header and accept the request, with possible side effects (e.g., deleting a record), and at the time the client gets the response, the browser could still “fail” the request, but the damage has already been done. What the browser does in those cases is to first send a preflight request, which is a HTTP OPTIONS request asking for permission to send the actual request. If the service answers that request allowing the call, only then the browser will send the user request to the service.

CORS in WCF

So let’s start with the “normal” requests. That’s actually fairly simple to implement – we can use an inspector to check the “Origin” header in the requests, and if it’s present (and we want to allow the cross-domain request) on the reply we add the “Access-Control-Allow-Origin” header. As usual, we’ll start with a simple example, and go from there. And to make the comparison between WCF and the version I wrote in ASP.NET Web API easier, let’s use the exact same contract as that one.

  1. [ServiceContract]
  2. public interface IValues
  3. {
  4.     [WebGet(UriTemplate = "values", ResponseFormat = WebMessageFormat.Json)]
  5.     List<string> GetValues();
  6.     [WebGet(UriTemplate = "values/{id}", ResponseFormat = WebMessageFormat.Json)]
  7.     string GetValue(string id);
  8.     [WebInvoke(UriTemplate = "/values", Method = "POST", ResponseFormat = WebMessageFormat.Json)]
  9.     void AddValue(string value);
  10.     [WebInvoke(UriTemplate = "/values/{id}", Method = "DELETE", ResponseFormat = WebMessageFormat.Json)]
  11.     void DeleteValue(string id);
  12.     [WebInvoke(UriTemplate = "/values/{id}", Method = "PUT", ResponseFormat = WebMessageFormat.Json)]
  13.     string UpdateValue(string id, string value);
  14. }

The implementation is exactly the same as in the Web API one, so I’ll leave it out. Now, we need one “tagging” attribute to indicate whether an operation can be called via cross-domain calls or not. We can use an empty operation behavior attribute, which will be easily accessible via the operation description later.

  1. public class CorsEnabledAttribute : Attribute, IOperationBehavior
  2. {
  3.     public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
  4.     {
  5.     }
  6.  
  7.     public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
  8.     {
  9.     }
  10.  
  11.     public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
  12.     {
  13.     }
  14.  
  15.     public void Validate(OperationDescription operationDescription)
  16.     {
  17.     }
  18. }

And by having the CorsEnabled attribute as a IOperationBehavior, it allows us to filter through he operations for which we should implement the CORS handshake in our endpoint behavior.

  1. class EnableCorsEndpointBehavior : IEndpointBehavior
  2. {
  3.     public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
  4.     {
  5.     }
  6.  
  7.     public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
  8.     {
  9.     }
  10.  
  11.     public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
  12.     {
  13.         List<OperationDescription> corsEnabledOperations = endpoint.Contract.Operations
  14.             .Where(o => o.Behaviors.Find<CorsEnabledAttribute>() != null)
  15.             .ToList();
  16.         endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new CorsEnabledMessageInspector(corsEnabledOperations));
  17.     }
  18.  
  19.     public void Validate(ServiceEndpoint endpoint)
  20.     {
  21.     }
  22. }

The inspector is divided in two parts: incoming requests and the verification whether the “Origin” header was sent and whether the operation for where the request is directed is one of those which are CORS-enabled. The first information we get via the HttpRequestMessageProperty property. The second one we could look at the request URI, but since the inspector is executed after the operation selector, that information is already available in the message properties via the WebHttpDispatchOperationSelector.HttpOperationNamePropertyName key. If those two conditions are met, then we return the value of the Origin header, which will be passed to the BeforeSendReply method of the inspector.

The second part, for the response, starts by looking at the correlation state returned by the AfterReceiveRequest method. If there is something, then the request had an Origin header, and we’ll use the HttpResponseMessageProperty on the reply message to send back the Access-Control-Allow-Origin method.

  1. class CorsEnabledMessageInspector : IDispatchMessageInspector
  2. {
  3.     private List<string> corsEnabledOperationNames;
  4.  
  5.     public CorsEnabledMessageInspector(List<OperationDescription> corsEnabledOperations)
  6.     {
  7.         this.corsEnabledOperationNames = corsEnabledOperations.Select(o => o.Name).ToList();
  8.     }
  9.  
  10.     public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
  11.     {
  12.         HttpRequestMessageProperty httpProp = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name];
  13.         object operationName;
  14.         request.Properties.TryGetValue(WebHttpDispatchOperationSelector.HttpOperationNamePropertyName, out operationName);
  15.         if (httpProp != null && operationName != null && this.corsEnabledOperationNames.Contains((string)operationName))
  16.         {
  17.             string origin = httpProp.Headers[CorsConstants.Origin];
  18.             if (origin != null)
  19.             {
  20.                 return origin;
  21.             }
  22.         }
  23.  
  24.         return null;
  25.     }
  26.  
  27.     public void BeforeSendReply(ref Message reply, object correlationState)
  28.     {
  29.         string origin = correlationState as string;
  30.         if (origin != null)
  31.         {
  32.             HttpResponseMessageProperty httpProp = null;
  33.             if (reply.Properties.ContainsKey(HttpResponseMessageProperty.Name))
  34.             {
  35.                 httpProp = (HttpResponseMessageProperty)reply.Properties[HttpResponseMessageProperty.Name];
  36.             }
  37.             else
  38.             {
  39.                 httpProp = new HttpResponseMessageProperty();
  40.                 reply.Properties.Add(HttpResponseMessageProperty.Name, httpProp);
  41.             }
  42.  
  43.             httpProp.Headers.Add(CorsConstants.AccessControlAllowOrigin, origin);
  44.         }
  45.     }
  46. }

And now, by decorating the GET operations with [CorsEnabled] and adding the EnableCorsEndpointBehavior to the endpoint, those operations can now be called via cross-domain.

Implementing preflight requests

The first part was easy. For the second part, we need to intercept the requests with the OPTIONS verb, and return the response immediately, without going to the operation. And that’s probably one of the biggest features missing in the WCF extensions – the ability to bypass the rest of the WCF pipeline at a given point. The first option is to use a custom reply channel (which, with anything in the channel layer, is really hard to write). The other option is to use a custom operation invoker which can bypass the actual operation. But the invoker by itself doesn’t work – on the invoker call you don’t have a reference to either the incoming request (to look for CORS headers) or the outgoing response (to set the response headers). Also, in order for the invoker to be called for OPTIONS requests (instead of the actual request), we needed to also have an operation selector which will map requests for OPTIONS verb to the actual operation. And it would also need some way to not map multiple operations which have the same URI template (but different verbs) to different operations, since OPTIONS will be the common ground there. And we’d also need to change the formatter so that the response for the OPTIONS request would be an empty response instead of the actual result of the operation… In short, not simple at all.

Another option, which I got the idea from the post about how to add dynamic operations from Zufilqar’s blog, is to not change the existing operations, but instead add new ones to handle the OPTIONS requests. Since I always try to avoid channels programming whenever possible, this seemed the best option given all the problems of using a custom invoker for the existing operations. Those operations actually need a custom invoker, but by making them operations with untyped messages (Message in, Message out), we don’t need a custom formatter, and we can access to the HTTP headers via the message properties as well.

To make this scenario simpler to use, let’s create a custom service host (and service host factory) to wrap the logic for creating the new operations. The service host will use as the contract type either the service type itself (if it is decorated with ServiceContractAttribute), or one interface the service type implements (and the interface is decorated with ServiceContractAttribute). More complex logic can be added if needed, but for this scenario, this is enough.

When the service host is being opened, we’ll add the single endpoint, find all the operations which are decorated with the CorsEnabled attribute, and for those, add a corresponding operation which deals with the preflight requests.

  1. class CorsEnabledServiceHostFactory : ServiceHostFactory
  2. {
  3.     protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
  4.     {
  5.         return new CorsEnabledServiceHost(serviceType, baseAddresses);
  6.     }
  7. }
  8.  
  9. class CorsEnabledServiceHost : ServiceHost
  10. {
  11.     Type contractType;
  12.     public CorsEnabledServiceHost(Type serviceType, Uri[] baseAddresses)
  13.         : base(serviceType, baseAddresses)
  14.     {
  15.         this.contractType = GetContractType(serviceType);
  16.     }
  17.  
  18.     protected override void OnOpening()
  19.     {
  20.         ServiceEndpoint endpoint = this.AddServiceEndpoint(this.contractType, new WebHttpBinding(), "");
  21.  
  22.         List<OperationDescription> corsEnabledOperations = endpoint.Contract.Operations
  23.             .Where(o => o.Behaviors.Find<CorsEnabledAttribute>() != null)
  24.             .ToList();
  25.  
  26.         AddPreflightOperationSelectors(endpoint, corsEnabledOperations);
  27.  
  28.         endpoint.Behaviors.Add(new WebHttpBehavior());
  29.         endpoint.Behaviors.Add(new EnableCorsEndpointBehavior());
  30.  
  31.         base.OnOpening();
  32.     }
  33.  
  34.     private Type GetContractType(Type serviceType)
  35.     {
  36.         if (HasServiceContract(serviceType))
  37.         {
  38.             return serviceType;
  39.         }
  40.  
  41.         Type[] possibleContractTypes = serviceType.GetInterfaces()
  42.             .Where(i => HasServiceContract(i))
  43.             .ToArray();
  44.  
  45.         switch (possibleContractTypes.Length)
  46.         {
  47.             case 0:
  48.                 throw new InvalidOperationException("Service type " + serviceType.FullName + " does not implement any interface decorated with the ServiceContractAttribute.");
  49.             case 1:
  50.                 return possibleContractTypes[0];
  51.             default:
  52.                 throw new InvalidOperationException("Service type " + serviceType.FullName + " implements multiple interfaces decorated with the ServiceContractAttribute, not supported by this factory.");
  53.         }
  54.     }
  55.  
  56.     private static bool HasServiceContract(Type type)
  57.     {
  58.         return Attribute.IsDefined(type, typeof(ServiceContractAttribute), false);
  59.     }
  60. }

In order to add the preflight operations, we first iterate over all the CORS-enabled operations which need to respond to the preflight request (GET requests don’t need those). For those operations, we first get the URI template for the operation, and normalize it (remove query string parameters, and remove the parameter lists replacing them with wildcards) so that two operations with similar URI templates (e.g., [WebInvoke(Method = “POST”, UriTemplate = “/products/{param1}?x={param2}”)] and [WebInvoke(Method = “DELETE”, UriTemplate = “/products/{id}”)]) will have only one new operation for the “/products/*” URI. If there is already an OPTIONS operation for the normalized URI we’ll add HTTP verb to it, otherwise we’ll create a new operation to handle the OPTIONS request.

  1. private void AddPreflightOperations(ServiceEndpoint endpoint, List<OperationDescription> corsOperations)
  2. {
  3.     Dictionary<string, PreflightOperationBehavior> uriTemplates = new Dictionary<string, PreflightOperationBehavior>(StringComparer.OrdinalIgnoreCase);
  4.  
  5.     foreach (var operation in corsOperations)
  6.     {
  7.         if (operation.Behaviors.Find<WebGetAttribute>() != null || operation.IsOneWay)
  8.         {
  9.             // no need to add preflight operation for GET requests, no support for 1-way messages
  10.             continue;
  11.         }
  12.  
  13.         string originalUriTemplate;
  14.         WebInvokeAttribute originalWia = operation.Behaviors.Find<WebInvokeAttribute>();
  15.  
  16.         if (originalWia != null && originalWia.UriTemplate != null)
  17.         {
  18.             originalUriTemplate = NormalizeTemplate(originalWia.UriTemplate);
  19.         }
  20.         else
  21.         {
  22.             originalUriTemplate = operation.Name;
  23.         }
  24.  
  25.         string originalMethod = originalWia != null && originalWia.Method != null ? originalWia.Method : "POST";
  26.  
  27.         if (uriTemplates.ContainsKey(originalUriTemplate))
  28.         {
  29.             // there is already an OPTIONS operation for this URI, we can reuse it
  30.             PreflightOperationBehavior operationBehavior = uriTemplates[originalUriTemplate];
  31.             operationBehavior.AddAllowedMethod(originalMethod);
  32.         }
  33.         else
  34.         {
  35.             ContractDescription contract = operation.DeclaringContract;
  36.             OperationDescription preflightOperation;
  37.             PreflightOperationBehavior preflightOperationBehavior;
  38.             CreatePreflightOperation(operation, originalUriTemplate, originalMethod, contract, out preflightOperation, out preflightOperationBehavior);
  39.             uriTemplates.Add(originalUriTemplate, preflightOperationBehavior);
  40.  
  41.             contract.Operations.Add(preflightOperation);
  42.         }
  43.     }
  44. }

Creating the preflight operation means creating a new operation description for the contract, adding two messages to it: an input message with a single body part of type Message, and an output message with a return value of the same type. We then use the same URI template as the original operation, and add a WebInvokeAttribute to the operation. We then add a DataContractSerializerOperationBehavior to the operation description, since it will give us a formatter which understands the (Message in, Message out) pattern. Finally, we add our custom operation behavior, which we’ll use to implement the operation invoker which will ultimately deal with the preflight request

  1. private static void CreatePreflightOperation(OperationDescription operation, string originalUriTemplate, string originalMethod, ContractDescription contract, out OperationDescription preflightOperation, out PreflightOperationBehavior preflightOperationBehavior)
  2. {
  3.     preflightOperation = new OperationDescription(operation.Name + CorsConstants.PreflightSuffix, contract);
  4.     MessageDescription inputMessage = new MessageDescription(operation.Messages[0].Action + CorsConstants.PreflightSuffix, MessageDirection.Input);
  5.     inputMessage.Body.Parts.Add(new MessagePartDescription("input", contract.Namespace) { Index = 0, Type = typeof(Message) });
  6.     preflightOperation.Messages.Add(inputMessage);
  7.     MessageDescription outputMessage = new MessageDescription(operation.Messages[1].Action + CorsConstants.PreflightSuffix, MessageDirection.Output);
  8.     outputMessage.Body.ReturnValue = new MessagePartDescription(preflightOperation.Name + "Return", contract.Namespace) { Type = typeof(Message) };
  9.     preflightOperation.Messages.Add(outputMessage);
  10.  
  11.     WebInvokeAttribute wia = new WebInvokeAttribute();
  12.     wia.UriTemplate = originalUriTemplate;
  13.     wia.Method = "OPTIONS";
  14.  
  15.     preflightOperation.Behaviors.Add(wia);
  16.     preflightOperation.Behaviors.Add(new DataContractSerializerOperationBehavior(preflightOperation));
  17.     preflightOperationBehavior = new PreflightOperationBehavior(preflightOperation);
  18.     preflightOperationBehavior.AddAllowedMethod(originalMethod);
  19.     preflightOperation.Behaviors.Add(preflightOperationBehavior);
  20. }

Finally, the custom invoker. The implementation of the IOperationInvoker interface is fairly trivial: allocate 1 input, only work with synchronous operations. The Invoke calls the operation to handle the preflight request: take the incoming message HttpRequestMessageProperty property, get any CORS-specific headers, then create an empty reply message, and add to it a HttpResponseMessageProperty property with the appropriate headers.

  1. class PreflightOperationInvoker : IOperationInvoker
  2. {
  3.     private string replyAction;
  4.     List<string> allowedHttpMethods;
  5.     
  6.     public PreflightOperationInvoker(string replyAction, List<string> allowedHttpMethods)
  7.     {
  8.         this.replyAction = replyAction;
  9.         this.allowedHttpMethods = allowedHttpMethods;
  10.     }
  11.  
  12.     public object[] AllocateInputs()
  13.     {
  14.         return new object[1];
  15.     }
  16.  
  17.     public object Invoke(object instance, object[] inputs, out object[] outputs)
  18.     {
  19.         Message input = (Message)inputs[0];
  20.         outputs = null;
  21.         return HandlePreflight(input);
  22.     }
  23.  
  24.     public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
  25.     {
  26.         throw new NotSupportedException("Only synchronous invocation");
  27.     }
  28.  
  29.     public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
  30.     {
  31.         throw new NotSupportedException("Only synchronous invocation");
  32.     }
  33.  
  34.     public bool IsSynchronous
  35.     {
  36.         get { return true; }
  37.     }
  38.  
  39.     Message HandlePreflight(Message input)
  40.     {
  41.         HttpRequestMessageProperty httpRequest = (HttpRequestMessageProperty)input.Properties[HttpRequestMessageProperty.Name];
  42.         string origin = httpRequest.Headers[CorsConstants.Origin];
  43.         string requestMethod = httpRequest.Headers[CorsConstants.AccessControlRequestMethod];
  44.         string requestHeaders = httpRequest.Headers[CorsConstants.AccessControlRequestHeaders];
  45.  
  46.         Message reply = Message.CreateMessage(MessageVersion.None, replyAction);
  47.         HttpResponseMessageProperty httpResponse = new HttpResponseMessageProperty();
  48.         reply.Properties.Add(HttpResponseMessageProperty.Name, httpResponse);
  49.  
  50.         httpResponse.SuppressEntityBody = true;
  51.         httpResponse.StatusCode = HttpStatusCode.OK;
  52.         if (origin != null)
  53.         {
  54.             httpResponse.Headers.Add(CorsConstants.AccessControlAllowOrigin, origin);
  55.         }
  56.  
  57.         if (requestMethod != null && this.allowedHttpMethods.Contains(requestMethod))
  58.         {
  59.             httpResponse.Headers.Add(CorsConstants.AccessControlAllowMethods, string.Join(",", this.allowedHttpMethods));
  60.         }
  61.  
  62.         if (requestHeaders != null)
  63.         {
  64.             httpResponse.Headers.Add(CorsConstants.AccessControlAllowHeaders, requestHeaders);
  65.         }
  66.  
  67.         return reply;
  68.     }
  69. }

That’s it. We can now use the custom service host factory to webhost our service, and use a page in another web service to call our service – as long as the browser supports CORS (which means the latest Chrome and Firefox, and IE 10 and above). You can find the code for a sample page at the sample in the code gallery.

Final thoughts: WCF vs. ASP.NET Web APIs

This is a good example to compare the extensibility between WCF and the ASP.NET Web APIs. The implementation of this scenario took a lot more code (and a non-negligible number of extension points) to be done in WCF compared with a similar solution in Web API. This is a specific HTTP scenario, and for those cases, Web APIs will likely be easier. But what I tried to do (and did) was to show that it can be done in WCF, so if you have an investment in that technology, you don’t need to hurry to make the change (if you’re starting a new project, and the focus of the project is HTTP only and the Web, then Web API would be a logical choice).

[Code in this post]