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
Related
I want to customize the request processing flow myself. I don't want to use the Controller under Asp.net core to process the request. But there is a premise that there must be a class method or a delegate (containing Request and response formal parameters), where all requests are processed
For response processing, I hope to use Asp.net core's default response processing method (if it can be done), such as Ajax requests, dynamic page output, response pictures, file downloads, etc.
Envisioned codeļ¼
var handler=HTTP.handler((req, res) => {
if(req.getHeader("x-requested-with")){
if(req.para("username")==null){
res.endError(403)
}else{
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World');
}
}else{
res.view('/test/index.cshtml');
}
});
You want to write custom middleware. It can be registered in Startup to be called as a part of request processing and will be called for all requests.
There is a HttpContext object so you can get access to the current request.
The docs have some good examples that you can modify.
Custom middleware documentation
I'm trying to connect to a finicky API using RestSharp. The API uses OAuth1.0 and on the initial Request Token requires oauth_callback parameter ONLY in the query and not in the Authentication Header (I have confirmed this with Postman).
When I construct the request this way:
var Authenticator = OAuth1Authenticator.ForRequestToken(mc_apiKey, mc_appsecret);
Authenticator.ParameterHandling = OAuthParameterHandling.HttpAuthorizationHeader;
Authenticator.SignatureMethod = OAuthSignatureMethod.PlainText;
client.Authenticator = Authenticator;
var request = new RestRequest(RequestToken, Method.POST);
string AuthorizationCallBackURL = string.Format(LoopbackCallback);
request.AddParameter(_oauthCallback, AuthorizationCallBackURL, ParameterType.QueryStringWithoutEncode);
and look at the logs on the server I see the query string in the Http call,
http://192.168.0.187:8080/xxxx/ws/oauth/initiate?oauth_callback=http://192.168.0.187:8080/provider_emailer/callback.jsp
but it is also in the Authentication header:
Headers:
{Accept=[application/json, text/json, text/x-json, text/javascript, application/xml, text/xml],
accept-encoding=[gzip, deflate],
Authorization=[OAuth oauth_callback="http://192.168.0.187:8080/provider_emailer/callback.jsp",
oauth_consumer_key="XXXXXXXXXXXX",
oauth_nonce="cei09xm04qetk2ce",
oauth_signature="XXXXXXXXXXXXXXXX",
oauth_signature_method="PLAINTEXT",
oauth_timestamp="1591197088",
oauth_version="1.0"],
Content-Length=[0], Content-Type=[null],
cookie=[JSESSIONID=C8C8DB501382F7D1E52FE436600094C0],
host=[192.168.0.187:8080], user-agent=[RestSharp/106.11.4.0]}
This causes a "NotAcceptable" response. The same request done with Postman without the callback parameter in the Authentication header works.
Am I doing something wrong? Is there a way to only get the callback in the query string?
That's tricky. I looked at the code and we don't set the callback URL to the workflow object when you use the overload without this parameter. So, you're doing it conceptually correct.
However, we must collect all the parameters (default and request parameters) plus OAuth parameters to build the OAuth signature. We then use the parameters collection and extract all of them that have a name starting with oauth_ or xauth_ to the request headers (in case you use HttpAuthorizationHeader) and by doing so, we put your query parameter to the header.
Apparently, that's not ideal and it looks like a bug, so I can suggest opening an issue in RestSharp repository. Should not be hard to fix.
I have a Cookie based authentication in my webapp to which a sub application make some ajax requests to get the data from db.
the issue is that if the user is not authenticated i redirect him to expired.html, in test mode if i just run in browser or postman an api call like example.com/api/test without getting first the authentication cookie i'm correctly redirected to expired.html. the issue comes when i try to call that api via ajax so by making a simple .get request as the following:
function getPlu(codplu, callback){
let api = 'https://www.example.it/api/plu/?codplu=' + codplu
$.get( api, callback );
}
getPlu('COPERTI', callback => {
...
});
i just get the response from api with code 302 and a .get to expired.html with code 304 but the user still is not redirected to expired.html
So as you can see the status code for that api request is 302 and location should be expired.html BUT it's not getting redirected.
Might it be that browser doesn't handle automatically ajax redirects and i need to do it via client-side (redirect if status.code == 302) or i could fix it via server side?
Here is how the authentication makes the redirect
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => {
options.Cookie.Name = "AUTH_TOKEN";
options.Cookie.MaxAge = TimeSpan.FromMinutes(120);
options.Events = new CookieAuthenticationEvents()
{
OnRedirectToLogin = (context) =>
{
context.HttpContext.Response.Redirect("https://www.example.it/vmenu/expired.html");
return Task.CompletedTask;
}
};
});
Just to make this answer more clear:
jQuery's ajax uses the XMLHttpRequest object and its methods to execute requests. XMLHttpRequest will follow redirects automatically. Since it's XMLHttpRequest who does that, jQuery's ajax function doesn't even know about it. It only receives the final response, which in the OP's case is 200 Ok (or 304 Not Modified as OP posted).
Also, since the request is made by jQuery/XMLHttpRequest, the client view is not changed if a request or a redirect is executed. Everything is only in the browser's "behind execution".
Since all redirects are executed automatically by XMLHttpRequest, and jQuery is not able to tell if a redirect was made, the most reliable way (and that's the most important thing to me) is handle it manually:
1 - On server side, when unauthenticated request, add a custom header to the response, and respond with 200 OK:
OnRedirectToLogin = (context) =>
{
context.Response.StatusCode = (int)System.Net.HttpStatusCode.OK;
context.Response.Headers.Add("X-Unauthenticated-Redirect", "https://www.example.it/vmenu/expired.html");
return Task.CompletedTask;
}
2 - On client side, just check if this custom header exists. If it does, redirect manually using window.location:
var redirectHeader = jqXHR.getResponseHeader('X-Unauthenticated-Redirect');
if (redirectHeader.length > 0) {
window.location = redirectHeader;
}
Just for reference, from XMLHttpRequest docs:
If the origin of the URL conveyed by the Location header is same origin with the XMLHttpRequest origin and the redirect does not violate infinite loop precautions, transparently follow the redirect while observing the same-origin request event rules.
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.
There is a authentication library that I have to use that helpfully does things like
Response.Redirect(url, false);
inside of it's method calls. I can't change this libraries code and it's fine for MVC style apps but in angular SPA -> WebApi apps this is just awful.
I really need a 401 otherwise I get into trouble with CORS when my angular scripts, using $http, try to call out to the auth server on another domain in response to the 302, that's if it even could as the Response.Redirect also sends down the object moved html and the angle brackets cause an error to be thrown.
Since I have to make the call to the auth library first the Response.Redirect is already in the response pipeline and so I need to clean it up to remove the body content and convert the 302 into a 401. I thought I could just:
return new HttpWebResponse(StatusCode.UnAuthorized){
Content = new StringContent("data");
}
but this just gets appended to the response and doesn't replace it plus I also need the Location: header which I can't seem to access via WebApi methods.
So instead I've had to do this in my ApiController:
var ctxw = this.Request.Properties["MS_HtpContext"] as HttpContextWrapper;
var ctx = ctxw.ApplicationInstance.Context;
var url = ctx.Response.RedirectLocation;
ctx.Response.ClearContent();
return new HttpWebResponse(StatusCode.UnAuthorized){
Content = new StringContent(url);
}
But this seems terrible and counter to webapi "feel". Plus I'm tied to the controller in doing this. I can't get the wrapper in a MessageHandler for example.
What I'd like to do is monitor the response for a given route in a message handler or in an AuthorizationFilterAttribute, if its a 302, I want to read it's headers, take what I want, wipe it and replace it with my own "fresh" response as a 401. How can I do this?
You might want to write your own ActionFilter and override its OnActionExecuted method where you can access HttpActionExecutedContext. From there, you can check response code, for example, and overwrite response with whatever you want.
Ref: https://msdn.microsoft.com/en-us/library/system.web.http.filters.actionfilterattribute.onactionexecuted%28v=vs.118%29.aspx#M:System.Web.Http.Filters.ActionFilterAttribute.OnActionExecuted%28System.Web.Http.Filters.HttpActionExecutedContext%29