20

I have a WCF REST service hosted within a Windows service and I would like to send the Access-Control-Allow-Origin HTTP header (defined as part of CORS) with every response.

My attempted solution was to have something like the following within an IDispatchMessageInspector implementation:

public void BeforeSendReply(ref Message reply, object correlationState)
{
    var httpResponse = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
    if (httpResponse != null)
    {
        // test of CORS
        httpResponse.Headers["Access-Control-Allow-Origin"] = "*";
    }
}

Normally this would work, but unfortunately my service also uses HTTP basic authorization, which means that when a request comes in without the Authorization header, WCF automatically sends a 401 response asking for credentials. Unfortunately WCF does not call my IDispatchMessageInspector during this initial exchange, so Access-Control-Allow-Origin header is not added to the initial exchange.

The problem occurs when I try to call the service from a browser. CORS specifies that cross-origin requests should only be allowed if the origin domain matches the domain listed in the Access-Control-Allow-Origin response header (* matches all domains). Unfortunately when the browser sees the initial 401 response without the Access-Control-Allow-Origin header, it prevents access (according to the same origin policy).

Is there any way add a header to the initial 401 response sent automatically by WCF?

Kevin
  • 7,234
  • 4
  • 25
  • 30

4 Answers4

24

This guy saved my day.

http://blogs.microsoft.co.il/blogs/idof/archive/2011/07.aspx

I am going to place some of his notes here, just in case that web page dies some day. (I hate finding "Your answer is right HERE" links, and then the link is dead.)

<behaviors> 
  <endpointBehaviors> 
    <behavior name="webSupport"> 
      <webHttp /> 
      <CorsSupport /> 
    </behavior> 
  </endpointBehaviors> 
</behaviors> 
<extensions> 
  <behaviorExtensions> 
    <add name="CorsSupport" type="WebHttpCors.CorsSupportBehaviorElement, WebHttpCors, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /> 
  </behaviorExtensions> 
</extensions> 
<services> 
  <service name="Service.JSonService"> 
    <endpoint address="http://localhost:8080" behaviorConfiguration="webSupport” binding="webHttpBinding" contract="Service.IJSonService" /> 
  </service> 
</services>

Now, you have to find his downloadable library called "WebHttpCors.dll".

But there is enough there (above) to help you google/bing your way to a resolution.

The part that was throwing me for a loop (in my scenario) is that IE was working, but Firefox was not working.

My originating page was:

http://localhost:53692/test/WCFCallTestViaJQ14.htm

So my service is at:

http://localhost:8002/MyWCFService/MyWCFMethodByWebGet?state=NC&city=Raleigh

So I had localhost <<-->> localhost traffic.

**** But the ports were different. (53692 and 8002) ****

IE was ok with it. Firefox was not ok with it.

Then you gotta remember that each browser handles their .Send() requests differently (inside JQUERY that is).

It all makes sense now.

//JavaScript snipplet from JQuery library

if (window.XMLHttpRequest) {

    returnObject = new XMLHttpRequest();

} else if (window.ActiveXObject) {

    returnObject = new ActiveXObject("Microsoft.XMLHTTP");

} else {

msg = "Your browser doesn't support AJAX!";

}

Here are some key words, phrases that I've been googling/binging that finally led me somewhere.

    Result: [Exception... "Component returned failure code: 0x80040111 (NS_ERROR_NOT_AVAILABLE) [nsIXMLHttpRequest.statusText]" nsresult: "0x80040111 (NS_ERROR_NOT_AVAILABLE)" location: "JS frame :: http://localhost:53692/test/WCFCallTestViaJQ14.htm :: HandleJQueryError :: line 326" data: no]


XMLHttpRequest Send "NS_ERROR_FAILURE"

JQuery Ajax WCF Self Hosted CORS JSON

NOW, YOU NEED TO READ HIS BLOG BLOGS TO UNDERSTAND WHAT THE CODE IS DOING:

For example, he says:

“Access-Control-Allow-Origin” header with the value of “*”

This may or may not be what you want. You may want to have better control of this value (headers) and the others (methods and the origins).

Development environment is one thing. (Use all the *'s you want).

Production is something else, you may want to tweak down those * values to something more discriminate. In a nutshell, you need to understand what CORS is actually doing for you in terms of security, and not just add a behavior that lets everything in.

  allowed-origins: '*'
  allowed-headers: '*'
  allowed-methods: '*'
granadaCoder
  • 21,474
  • 7
  • 81
  • 117
6

To achieve what you want you need to handle the authorization yourself which is possible by impelementing + registering a HttpModule... there you would issue the 401 and along with it any http header you want... there is even a sample implementation here on SO - see Adding basic HTTP auth to a WCF REST service

EDIT - after comment from OP:

Since the OP's comment says that he is self-hosting the solution is not with HTTPModule but actually with IDispatchMessageInspector.BeforeSendReply and with IDispatchMessageInspector.AfterReceiveRequest.

The Authorization must be configured to "None" and custom implemented/handled in IDispatchMessageInspector - this way you can add any header when issuing a 401 . Otherwise the runtime handling Basic Auth wouldn't call your IDispatchMessageInspector before proper/positive Auth.

Although this works BEWARE that this means you implement security-sensitiv code yourself and thus need to take appriopriate measure to ensure its proper implementation...

Community
  • 1
  • 1
Yahia
  • 67,016
  • 7
  • 102
  • 131
  • Thanks for the reply. My service needs to be a self-hosted Windows service, not IIS hosted. The HTTP basic auth stuff in my service works great. CORS also works fine (assuming I disable basic auth). The problem only occurs when mixing HTTP basic and CORS on a self-hosted service. If I were to host the service in IIS, I would configure IIS to send the CORS response headers on every request and use an HttpModule for the HTTP basic auth. – Kevin Aug 31 '11 at 14:33
  • ok - see my edit... your solution was already half the way... you need to implement one other method and change the configuration... – Yahia Aug 31 '11 at 14:54
  • Setting clientCredentialType="None" and handing doing the inspection manually almost works. You can send the 401 reply, but WCF does not allow you to set the WWW-Authenticate response header. Attempting to set WWW-Authenticate within code causes WCF to return a 504. Without WWW-Authenticate the browser will not prompt for credentials. – Kevin Aug 31 '11 at 17:42
  • sorry, but then that's end of the line for me - I have no further ideas... except hosting in IIS and using the `HttpModule` approach... – Yahia Aug 31 '11 at 18:30
  • Yeah, I suspect it might not be possible. I also tried using HttpListener instead of WCF and it has the same WWW-Authenticate problem. Thanks for the ideas. – Kevin Aug 31 '11 at 20:39
  • Same problem for me also. I need to host in IIS. surly will look into the http handler implementation. But any laternative because i am beginer to WCF , REST etc so not able to implement so much deplth now itself. Like Kevin told my service will not promt for credentials if we access directly from browser – Akhil Feb 10 '14 at 12:16
1

Adding the following line to the first method that gets called in the WCF Service worked for me.

WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");

Requires the following import

System.ServiceModel.Web;

Refer this for original answer

PoomaniGP
  • 48
  • 1
  • 10
0

I tried many ways but could not find anything and then suddenly in the end I came to know that headers should be sent through OPTIONS request only and then I found some helpful SO code here! This one resolves my problem completely.

Actually the point here is that you have to add headers in OPTIONS request along with just a 200 OK response and thats what is done on this link.

ZF007
  • 3,318
  • 8
  • 26
  • 39