CORS Support within WCF REST Services - c#

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?

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: '*'

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...

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

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.

Related

Unexpected behavior with CORS on .NET Core

I have added the following to my service configuration.
services.AddCors(options
=> options.AddDefaultPolicy(builder
=> builder
//.AllowAnyHeader()
.WithHeaders("header-that-nobody-knows")
.WithOrigins("http://localhost:44304")));
My expectation was that the calls would bounce (as I don't add header-that-nobody-knows to my headers). However, the request is carried out just as if AllowAnyHeader() was set.
Manipulating the port, domain or protocol in WithOrigins() produces the expected result, so the config seems to be wired up properly. I suspect it's a special case somehow because I'm getting unexpected behavior with WithMetod() when it comes to GET and POST (while other methods are blocked/allowed depending on the paramers passed).
Checking MSDN gave nothing I recon as explanation.
I doubt that it matters but for completeness sake, here's the Angular code invoking the call.
let url = "https://localhost:44301/security/corstest?input=blobb";
this.http.get<any>(url).subscribe(
next => console.log("next", next),
err => console.warn("err", err));
The action method looks as below.
[HttpGet("corstest")]
public IActionResult CorsTest([FromQuery] string input)
{
return Ok(new { data = input + " indeed..." });
}
When you try to send a request to a cross-origin URL with a "non-standard" header,
the browser will perform a preflight OPTIONS request with the Access-Control-Request-Headers header that contains the non-standard headers.
OPTIONS /corstest
Access-Control-Request-Method: GET
Access-Control-Request-Headers: header-that-nobody-knows
Origin: https://my.api
ASP.NET Core inspects this value and checks if the CORS policy has AllowAnyHeader or if it explicitly allows it with .WithHeaders, if not it will issue a non-200 response and the browser will refuse to send the actual request.
So, not adding header-that-nobody-knows to request headers doesn't mean ASP.NET Core will refuse to serve the request, it means if you set header-that-nobody-knows header in a cross-origin request, it will allow it instead of issuing a non-200 response (assuming you allowed it with WithHeaders or AllowAllHeaders)
So in a nutshell:
You have to allow some/all origins + some/all headers at minimum for a CORS policy to take effect.
Browser expects both Access-Control-Allow-Headers and Access-Control-Allow-Origin in the preflight request to match the main request.
You can only send a subset (which includes 0) of the allowed headers.
References
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests

Angular 6 headers with post request does't work

I'm using Angular 6 for my front-end and a Web API based on Core2 for my server. I wrote this code and this worked when I used ASP.NET client side, but with Angular I have some troubles.
services.AddMvc():
services.AddCors(o => o.AddPolicy("FreePolicy", builder =>
{
builder.WithHeaders(<redacted>)
//builder.AllowAnyHeader()
.AllowCredentials()
.AllowAnyMethod()
.AllowAnyOrigin();
}));
If I comment builder.WithHeaders and uncomment builder.AllowAnyHeader the errors go away, but otherwise Angular crashes.
My headers:
This looks like the place with my error because "MaybeUnknown". You can see brackets around the value, so this looks like an array. Can I fix it and will it solve my problem?
I'm getting errors after my post request on a server if I don't uncomment builder.AllowAnyHeader.
UPDATE
My headers on server side
MY headers on client
UPDATE 2
Configure method
UPDATE 3
It's clear from the latest screenshot you've posted, which shows the HTTP request that's being made through Chrome, that you have a missing header in your WithHeaders call. If you look at Access-Control-Request-Headers in the request, you'll see it contains four headers:
apiss
client-id
zump-api-version
content-type
However, your WithHeaders call does not include Content-Type, so you'll need to add that:
builder.WithHeaders("client-id", "zump-api-version", "apiss", "content-type")
...
Note: This is all case-insensitive, so you can case it in whichever way you'd prefer.
There's more information about this in the MDN docs: Access-Control-Allow-Headers, which includes the following explanation:
Note that certain headers are always allowed: Accept, Accept-Language, Content-Language, Content-Type (but only with a MIME type of its parsed value (ignoring parameters) of either application/x-www-form-urlencoded, multipart/form-data, or text/plain). These are called the simple headers, and you don't need to specify them explicitly.
This explains why you don't have to specify Accept (it's a "simple header"). You do have to specify Content-Type in your example because it is neither of the three MIME types referenced in the statement above.

Credentials flag is 'true', but the 'Access-Control-Allow-Credentials

I am trying to connect to a ASP.NET Web-API Web Service from an AngularJS page and I am getting the following
Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is ''. It must be 'true' to allow credentials. Origin 'http://localhost:221' is therefore not allowed access.
var cors = new EnableCorsAttribute("http://localhost:221", "*","GET,PUT,POST,DELETE");
config.EnableCors(cors);
Using this AngularJS
$http({
method: 'GET',
url: 'http://localhost:1980/api/investors/62632',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
withCredentials: true
// withCredentials: true,
}).then(function onUserComplete(response) {
// this callback will be called asynchronously
// when the response is available
}, function onError(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
After reading many articles I add this to the web.config
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="http://localhost:221" />
</customHeaders>
</httpProtocol>
and I get this error message
The 'Access-Control-Allow-Origin' header contains multiple values localhost:221, localhost:221, but only one is allowed. Origin localhost:221 is therefore not allowed access.
Which really doesn't make any sense as I have added it once and it doesn't find it but I add it to web.config and get an error saying its been added multiple times. I have read many articles and can't seem to find the right answer. I am using Google Chrome. Would be very grateful for help as I am pulling my hair out right now.
For whom, who uses WebApiConfig.cs:
config.EnableCors(new EnableCorsAttribute("*", "*", "*") { SupportsCredentials = true });
The header is added twice once by the code and the other by the web.config. The CORS support is used to allow for the addition of headers for CORS purposes. The configuration custom headers also add response headers to any request, so you may want to remove the config setting.
var cors = new EnableCorsAttribute..
<customHeaders>
<add name="Access-Control-Allow-Origin" value="http://localhost:221" />
</customHeaders>
Since both of those areas are adding the same origin twice, you get the multiple values on the header.
When making an AJAX call with the parameter withCredentials: true, the response header should have the Access-Control-Allow-Credentials = true. You need to add that via code using SupportsCredentials = true for the CORS attributes. Otherwise you will get the error
"Credentials flag is 'true', but the 'Access-Control-Allow-Credentials is ''"
For more information, on the withCredential parameter and the response header look at this article:
http://www.ozkary.com/2015/12/api-oauth-token-access-control-allow-credentials.html
hope it helps.
I came across this question while trying to hit a webapi on .net core from an angular2 app. I had to add AllowCredentials() to the cors configuration in my Configure method in the Startup.cs to look like the following.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
app.UseCors(builder =>
builder
.AllowCredentials()
.WithOrigins("http://localhost:3000"));
...
}
Try the method outlined here for preflight requests:
enabling cross-origin resource sharing on IIS7
And use the Chrome extension Postman or Fiddler for easier debugging of CORS. I'm willing to bet that you are adding the header twice, but without your entire code, it is difficult to debug. (heck, CORS is difficult to debug even with the code).
To me, it appears that you shouldn't have both the web.config setting as well as the global EnableCors() attribute - this causes the doubles.
You don't appear to be adding the Access-Control-Allow-Credentials anywhere server side, but it might be added by the AllowCors attribute, I am not sure. (I am partial to handling CORS in OWIN myself)

"Method not allowed" error coming while accessing a WCF from windows application

i have created an Ajax enabled WCF in my web project with a simple method of returning message "Hello World" and have hosted it in the IIS. When i call the WCF from browser it is working and showing the message like
{"d":"Hello World"}
I created a windows application and added the code as below to access the wcf
BasicHttpBinding binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
EndpointAddress epa = new EndpointAddress("http://localhost:60/wcf");
ServiceReference2.PurchaseWCFClient objk = new ServiceReference2.PurchaseWCFClient(binding,epa);
label1.Text = objk.DoWork();
But this code
label1.Text = objk.DoWork();
is returning an error saying that "The remote server returned an unexpected response: (405) Method Not Allowed."
i searched a lot in google and tried many ways but nothing works. I am new to WCF and don't have much understating of WCF.
the same function is working if i call WCF from the same project using javascript call or ajax call
this is what my purchaseWCF.svc.cs contains
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class PurchaseWCF
{
[WebGet()]
[OperationContract]
public string DoWork()
{
return "Hello World";
}
}
My web.config contains
<service name="InfraERP.WCF.PurchaseWCF">
<endpoint address="" behaviorConfiguration="InfraERP.WCF.PurchaseWCFAspNetAjaxBehavior"
binding="webHttpBinding" contract="InfraERP.WCF.PurchaseWCF" />
</service>
<behavior name="InfraERP.WCF.PurchaseWCFAspNetAjaxBehavior">
<enableWebScript />
Initially i tried by using like this
ServiceReference2.PurchaseWCFClient objk = new ServiceReference2.PurchaseWCFClient();
at this time i got error like this "Could not find default endpoint element that references contract 'ServiceReference2.PurchaseWCF' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this contract could be found in the client element."
after that referring to some sites i added code as above and got the "Method not allowed" error
i have wasted one complete day on this... Please help me to find out the problem...
Possible ways to solve your issue,
(i)Remove the Reference completely from your Service references, also remove manually from the config.
(ii)Add the service reference again, check for the Contract name in your service and your client config.

Catch-22 prevents streamed TCP WCF service securable by WIF; ruining my Christmas, mental health

I have a requirement to secure a streamed WCF net.tcp service endpoint using WIF. It should authenticate incoming calls against our token server. The service is streamed because it is designed to transfer large amounts of data n stuff.
This appears to be impossible. And if I can't get around the catch, my Christmas will be ruined and I'll drink myself to death in a gutter while merry shoppers step over my slowly cooling body. Totes serious, you guys.
Why is this impossible? Here's the Catch-22.
On the client, I need to create a channel with the GenericXmlSecurityToken I get from our token server. No problemo.
// people around here hate the Framework Design Guidelines.
var token = Authentication.Current._Token;
var service = base.ChannelFactory.CreateChannelWithIssuedToken(token);
return service.Derp();
Did I say "no problemo"? Problemo. In fact, NullReferenceException style problemo.
"Bro, " I asked the Framework, "do you even null check?" The Framework was silent, so I disassembled and found that
((IChannel)(object)tChannel).
GetProperty<ChannelParameterCollection>().
Add(federatedClientCredentialsParameter);
was the source of the exception, and that the GetProperty call was returning null. So, WTF? Turns out that if I turn on Message security and set the client credential type to IssuedToken then this property now exists in the ClientFactory (protip: There is no "SetProperty" equivalent in IChannel, the bastard).
<binding name="OMGWTFLOL22" transferMode="Streamed" >
<security mode="Message">
<message clientCredentialType="IssuedToken"/>
</security>
</binding>
Sweet. No more NREs. However, now my client is faulted at birth (still love him, tho). Digging through WCF diagnostics (protip: make your worst enemies do this after crushing them and driving them before you but right before enjoying the lamentations of their women and children), I see it's because of a security mismatch between the server and client.
The requested upgrade is not supported by 'net.tcp://localhost:49627/MyService'. This could be due to mismatched bindings (for example security enabled on the client and not on the server).
Checking the host's diags (again: crush, drive, read logs, enjoy lamentations), I see this is true
Protocol Type application/ssl-tls was sent to a service that does not support that type of upgrade.
"Well, self," I says, "I'll just turn on Message security on the host!" And I do. If you want to know what it looks like, it's an exact copy of the client config. Look up.
Result: Kaboom.
The binding ('NetTcpBinding','http://tempuri.org/') supports streaming which cannot be configured together with message level security. Consider choosing a different transfer mode or choosing the transport level security.
So, my host cannot be both streamed and secured via tokens. Catch-22.
tl;dr: How can I secure a streamed net.tcp WCF endpoint using WIF???
WCF has gotchas in a few areas with streaming (I'm looking at you, MTOM1) due to a fundamental issue in how it fails to perform preauthentication the way most people would think that should work (it only affects subsequent requests for that channel, not the first request) Ok, so this is not exactly your issue but please follow along as I will get to yours at the end. Normally the HTTP challenge works like this:
client hits server anonymously
server says, sorry, 401, I need authentication
client hits server with authentication token
server accepts.
Now, if you ever try to enable MTOM streaming on an WCF endpoint on the server, it will not complain. But, when you configure it on the client proxy (as you should, they must match bindings) it will explode in a fiery death. The reason for this is that the above sequence of events that WCF is trying to prevent is this:
client streams 100MB file to server anonymously in a single POST
server says sorry, 401, I need authentication
client again streams 100MB file to server with an authentication header
server accepts.
Notice that you just sent 200MB to the server when you only needed to send 100MB. Well, this is the problem. The answer is to send the authentication on the first attempt but this is not possible in WCF without writing a custom behaviour. Anyway, I digress.
Your Problem
First up, let me tell you that what you're trying is impossible2. Now, in order for you to stop spinning your wheels, let me tell you why:
It strikes me that you are now wandering in a similar class of problem. If you enable message level security, the client must load the entire stream of data into memory before it can actually close out the message with the usual hash function and xml signature required by ws-security. If it has to read the entire stream to sign the single message (which is not really a message, but it's a single continuous stream) then you can see the problem here. WCF will have to stream it once "locally" to compute the message security, then stream it again to send it to the server. This is clearly a silly thing, so WCF does not permit message level security for streaming data.
So, the simple answer here is that you should send the token either as a parameter to the initial web service, or as a SOAP header and use a custom behaviour to validate it. You cannot use WS-Security to do this. Frankly, this is not just a WCF issue - I cannot see how it could practically work for any other stacks.
Solving the MTOM Problem
This is just for an example how I solved my MTOM streaming issue for basic authentication, so perhaps you could take the guts of this and implement something similar for your issue. The crux of it is that in order to enable your custom message inspector, you have to disable all notion of security on the client proxy (it remains enabled on the server,) apart from transport level (SSL):
this._contentService.Endpoint.Behaviors.Add(
new BasicAuthenticationBehavior(
username: this.Settings.HttpUser,
password: this.Settings.HttpPass));
var binding = (BasicHttpBinding)this._contentService.Endpoint.Binding;
binding.Security.Mode = BasicHttpSecurityMode.Transport; // SSL only
binding.Security.Transport.ClientCredentialType =
HttpClientCredentialType.None; // Do not provide
Note that I have turned off transport security here because I will be providing that myself using a message inspector and custom behaviour:
internal class BasicAuthenticationBehavior : IEndpointBehavior
{
private readonly string _username;
private readonly string _password;
public BasicAuthenticationBehavior(string username, string password)
{
this._username = username;
this._password = password;
}
public void AddBindingParameters(ServiceEndpoint endpoint,
BindingParameterCollection bindingParameters) { }
public void ApplyClientBehavior(ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{
var inspector = new BasicAuthenticationInspector(
this._username, this._password);
clientRuntime.MessageInspectors.Add(inspector);
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
}
internal class BasicAuthenticationInspector : IClientMessageInspector
{
private readonly string _username;
private readonly string _password;
public BasicAuthenticationInspector(string username, string password)
{
this._username = username;
this._password = password;
}
public void AfterReceiveReply(ref Message reply,
object correlationState) { }
public object BeforeSendRequest(ref Message request,
IClientChannel channel)
{
// we add the headers manually rather than using credentials
// due to proxying issues, and with the 101-continue http verb
var authInfo = Convert.ToBase64String(
Encoding.Default.GetBytes(this._username + ":" + this._password));
var messageProperty = new HttpRequestMessageProperty();
messageProperty.Headers.Add("Authorization", "Basic " + authInfo);
request.Properties[HttpRequestMessageProperty.Name] = messageProperty;
return null;
}
}
So, this example is for anyone who is suffering with the MTOM issue, but also as a skeleton for you to implement something similar to authenticate your token generated by the primary WIF-secured token service.
Hope this helps.
(1) Large Data and Streaming
(2) Message Security in WCF (see "disadvantages.")

Categories