Web API CORS requests and browser cache - asp.net-web-api

We're using web api cors requests in our application and many api calls are cacheable by the clients, i.e. browsers.
Chrome and Firefox honours the cache control headers and caches the responses as they are supposed to but Internet Explorer and Safari always performs new requests.
Controller:
[EnableCors("*", "*", "*")]
public HttpResponseMessage Get()
{
var response = Request.CreateResponse(HttpStatusCode.OK, "test");
response.Headers.CacheControl = new CacheControlHeaderValue
{
Public = true,
MaxAge = DateTime.UtcNow.AddDays(7) - DateTime.UtcNow
};
response.Headers.ETag = new EntityTagHeaderValue("\"" + Guid.NewGuid().ToString() + "\"");
return response;
}
JQuery ajax request (issued from another domain):
$.ajax({
type: 'GET',
url: 'http://next.b.se/api/tests'
}).done(function (data) {
$("#v").text("Done!");
}).error(function (jqXHR, textStatus, errorThrown) {
$("#v").text("Error");
});
Request (from fiddler):
GET http://next.b.se/api/tests HTTP/1.1
Referer: http://hitta.a.se/local.html
Accept: */*
Accept-Language: en,sv-SE;q=0.5
Origin: http://hitta.a.se
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
Host: next.b.se
DNT: 1
Connection: Keep-Alive
Response (from fiddler):
HTTP/1.1 200 OK
Cache-Control: public, max-age=604800
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
ETag: "4bfa551b-61db-42cb-8877-25f20a0ec94d"
Vary: Accept-Encoding
Server: Microsoft-IIS/7.5
Access-Control-Allow-Origin: *
Date: Mon, 09 Jun 2014 10:58:12 GMT
Content-Length: 192
When loading the page hitta.a.se/local.html, from which the cross domain request is done, the first time after cleaning the browser cache all browsers (IE, CHrome, FF and Safari) makes a request to the api endpoint as expected.
When loading the page hitta.a.se/local.html the second time in a new tab and hitting return Chrome and FF gets the cached version, i.e. no request is issued to the api (as expected due to the cache-control headers) according to Fiddler and dev tools. The problem is that IE issues a new request, it does not use the cached version and it doesn't issue a conditional get using the etag and if-none-match header. I know that the api doesn't handle if-none-match requests but the problem is that the browser doesn't perform a conditional request at all.
Which is the appropriate approach if we would like IE and Safari to use the cached responses?
Notice! This question is posted on codeplex, https://aspnetwebstack.codeplex.com/discussions/548035, as well. I don't know where the web api gurus are most active!
UPDATE 2014-06-10
If we change the api call to use JSONP instead of Ajax Internet Explorer caches the response according to the cache headers.
JQuery JSONP request:
$.ajax({
type: 'GET',
url: 'http://next.b.se/api/tests',
dataType: 'jsonp',
contentType: 'text/javascript',
jsonpCallback: 'p',
cache: true
}).done(function (data) {
$("#v").text(data.v);
}).error(function (jqXHR, textStatus, errorThrown) {
$("#v").text("Error");
});
Safari still performs a new request. Any known workarounds for Safari? I.e. force Safari to honour the cache headers either on a cross domain XHR request or a JSONP request?
BR,
Max

Related

spring 4 and CSRF

I use spring boot, spring rest and spring security in a single page application.
With spring 4, CSRF is enable by default.
I wrote a class who extend WebSecurityConfigureAdapter
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/rest/**").authenticated();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.formLogin().successHandler(authenticationSuccessHandler);
http.formLogin().failureHandler(authenticationFailureHandler);
http.logout().logoutSuccessUrl("/");
// tried with and without... same issue
http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
}
CsrfTokenResponseHeaderBindingFilter come from
https://github.com/aditzel/spring-security-csrf-filter/blob/master/src/main/java/com/allanditzel/springframework/security/web/csrf/CsrfTokenResponseHeaderBindingFilter.java
I use only html (so no jsp, no xhmtl...) page only.
I do ajax call and feed my html.
Request header
POST /login HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 30
Pragma: no-cache
Cache-Control: no-cache
Accept: */*
Origin: http://localhost:8080
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost:8080/
Accept-Encoding: gzip, deflate
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: JSESSIONID=B05ED2676EC1637D74AC7622E018C9FD
In my form i have
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
When I try to log, i get
{"timestamp":1444179957867,"status":403,"error":"Forbidden","message":"Expected CSRF token not found. Has your session expired?","path":"/login"}
When the log fail, this command return null
jqXHR.getResponseHeader('X-CSRF-TOKEN')
ajax call
var data = 'username=' + $('#username').val() + '&password=' + $('#password').val();
$.ajax({
data: data,
timeout: 1000,
type: 'POST',
url: '/login'
}).done(function (data, textStatus, jqXHR) {
window.location = "main.html";
}).fail(function (jqXHR, textStatus, errorThrown) {
});
});
EDIT
In the browser when i connect to localhost:8080.
i see in the header answer
X-CSRF-HEADER:X-CSRF-TOKEN
X-CSRF-PARAM:_csrf
X-CSRF-TOKEN:0d5bf042-a30f-4f2e-a99c-51ed512f811a
How to get this information in JS? I need to put it in my post call.
There isn't enough information to debug this issue. You should post the following information:
Your XML/Java Spring Security Configuration.
Your Spring Web Configuration. Are you using JSPs or XHTML? Are you sure your view model is correct? Maybe your having issues with the web module not sending the token.
The actual http request headers/body. Is the request actually correct or is spring just not picking up the token?

AJAX CORS webAPI request fails even after the preflight OPTIONS succeeds

I am trying to make CORS AJAX "GET" call to an web API service hosted in a test server. webAPI URL = http:xxx:xxx:xxx:xxx/api/v1/jobs
I have the following lines of code in WebAPIConfig.cs
var cors = new EnableCorsAttribute("*","*","*");
config.EnableCors(cors);
AJAX request (from local)
$.ajax({
type: "GET",
datatype: "JSON",
url: http: xxx: xxx: xxx: xxx / api / v1 / jobs,
contentType: "application/json";
charset = utf - 8 ",
accept: 'application/json',
beforeSend: BH,
success: callback
}).done(function (data) {
var str = data.job_id + ': ' + data.job_name;
$('#responsevalue').text(str);
}).error(function (jqXHR, textStatus, errorThrown) {
$('#responsevalue').text(jqXHR.status + "::" + jqXHR.statusText + "::" + jqXHR.responseText );
});
In Fiddler, I can see the pre-flight request with OPTIONS being sent and the response with 200.
Fiddler Request Headers:
OPTIONS http:xxx:xxx:xxx:xxx/api/v1/jobs HTTP/1.1
Host: 50.17.211.226
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: http://localhost:55346
Access-Control-Request-Method: GET
Access-Control-Request-Headers: requestdateutc,requestverificationtoken
Connection: keep-alive
Fiddler Response Headers
HTTP/1.1 200 OK
Server: Microsoft-IIS/7.5
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Accept
Access-Control-Max-Age: 1728000
X-Powered-By: ASP.NET
Date: Thu, 20 Nov 2014 14:21:50 GMT
Content-Length: 0
In Firebug, i can see the following details:
Firebug Request Headers:
OPTIONS http:xxx.xxx.xxx.xxx/api/v1/jobs HTTP/1.1
Host: xxx.xxx.xxx.xxx
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20100101 Firefox/31.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Origin: http:localhost:55346
Access-Control-Request-Method: GET
Access-Control-Request-Headers: requestdateutc,requestverificationtoken
Connection: keep-alive
Firebug Response Headers:
HTTP/1.1 200 OK
Server: Microsoft-IIS/7.5
Access-Control-Allow-Origin: *, *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Accept, Content-Type
Access-Control-Max-Age: 1728000
X-Powered-By: ASP.NET
Date: Thu, 20 Nov 2014 18:08:36 GMT
Content-Length: 0
I read lot of documentation here and in other places. It seems pretty simple and works for everyone (except me).
Final note: I tested the API with the html page residing in side the test server an it worked fine. Meaning the service and the web page both residing in the same domain.
Additional Info: Browser: Firefox, ASP.NET 4.5, web API 2.2, VS2013 Express
Thanks in advance and any help will be much appreciated.
In your response, it says the allowed headers are Content-Type, Accept, Content-Type, but you are asking for requestdateutc,requestverificationtoken. Try explicitly allowing those headers.

Can't set a header using Cors and Ajax

Hello I am trying to do a http request with Basic Auth, but I can't set the header authorization and it is allowed in server.
Ajax :
$.ajax({
xhrFields: { withCredentials: true },
beforeSend: function(xhr){xhr.setRequestHeader('authorization', 'Basic cmFmmFuQHBoaWlubm92YXRpb25zLmNv=');},
url : 'http://www.vozi.dev.br/api/audio',
type: 'POST',
data: JSON.stringify(sender),
dataType: 'json',
contentType: 'application/json',
success : function(data, textStatus, jqXHR) {
//do something
}
});
Http Request Header:
Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8,pt;q=0.6
Access-Control-Request-Headers:accept, authorization, content-type
Access-Control-Request-Method:POST
Connection:keep-alive
Host:www.vozi.dev.br
Origin:http://localhost:8080
Referer:http://localhost:8080/act_text.jsp
User-Agent:Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36
Http Response Header:
Access-Control-Allow-Headers:accept, authorization, content-type
Access-Control-Allow-Methods:GET, POST, OPTIONS
Access-Control-Allow-Origin:*
cache-control:no-cache
Connection:Keep-Alive
Content-Type:text/html; charset=UTF-8
Date:Wed, 14 May 2014 20:15:53 GMT
Keep-Alive:timeout=5, max=100
Server:Apache/2.4.6 (Ubuntu)
Set-Cookie:PHPSESSID=k6gg748e47b2fv67; path=/
Transfer-Encoding:chunked
www-authenticate:Basic realm="Secured Area"
x-debug-token:5373cef9430fe
X-Powered-By:PHP/5.5.3-1ubuntu2
Error :
OPTIONS http://www.vozi.dev.br/api/audio 401 (A Token was not found in the SecurityContext.) jquery.js:8706
OPTIONS http://www.vozi.dev.br/api/audio Invalid HTTP status code 401 jquery.js:8706
XMLHttpRequest cannot load http://www.vozi.dev.br/api/audio. Invalid HTTP status code 401
I assume your having this issue with IE10 or IE11, This is not an issue with Chrome.
IE doesn't send authorization headers with OPTIONS request, so on server side if you enable Windows integrated authentication, it does reject the OPTIONS request.
I have this workaround posted on another stackoverflow question
I find out that i can't use
Access-Control-Allow-Origin:*
if I am using
withCredentials: true
Is necessary to set the origin.

angular js http request from aspx method

I have a page locations.aspx that has a method behind it in locations.aspx/getData. when I use the code
$http.jsonp($scope.url)
.success(function (data, status, headers, config) {
alert(data);
}).error(function (data, status, headers, config) {
$scope.status = status;
});
with the $scope.url being locations.aspx/getData it loads the html page of the aspx page but doesn't access the method. I can access the method using
$.ajax({
type: "POST",
url: $scope.url,
data: {},
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (p) {
var temp = $.parseJSON(p.d);
$scope.allItems.push(temp);
},
error: function () {
alert('error');
}
});
but the data never updates or binds on the view side. An example on the html is
<select ng-model="selectLocation" id="selectLocation" ng-change="onLocationChange()">
<option></option>
<option ng-repeat="l in allItems">{{l.location}}</option>
</select>
After the ajax call allItems array does have an item in it but the view never updates.
My ajax call headers are
Request URL:localhost:41796/locations.aspx/getData
Request Method:POST
Status Code:200 OK
Request Headersview source
Accept:application/json, text/javascript, /; q=0.01
Content-Type:application/json; charset=utf-8
Response Headersview source
Cache-Control:private, max-age=0
Connection:Close
Content-Length:270
Content-Type:application/json; charset=utf-8
and my $http headers are
Request URL:localhost:41796/locations.aspx/getData
Request Method:GET
Status Code:200 OK
Request Headersview parsed
GET /locations.aspx/getData HTTP/1.1
Host: localhost:41796
Connection: keep-alive
Response Headersview parsed
HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 7177
Connection: Close
Based on the discussion above, it seems you're using ASP.NET and running into specific issues with it.
ASP.NET can be pretty picky sometimes about what it will parse and what it won't -- especially when it comes to MVC (which it doesn't appear that you're using here, but perhaps [WebMethod] (what I assume you're using) has the same kinds of issues).
Instead of messing with jQuery's $.ajax, you can use Angular's $http in the same way:
$http({
method: 'POST',
url: $scope.url,
contentType: 'application/json; charset=utf-8'
})
.success(function (data, status, headers, config) {
alert(data);
})
.error(function (data, status, headers, config) {
$scope.status = status;
});
Check this: http://docs.angularjs.org/api/ng.$http#Parameters for more information.

JQuery-Ajax headers issue

I got a problem with the headers when i'm trying to do a POST Request with JSON
This is the code:
$.ajax({
type: "POST",
url: url,
data: jsonData,
dataType: 'json',
beforeSend: function(xhrObj){
xhrObj.setRequestHeader("Content-Type","application/json");
xhrObj.setRequestHeader("Accept","application/json");
},
error: function(){
alert("Fail");
},
success: function(){
alert("Success");
}
});
And this are the Request Headers displayed by Firebug.
OPTIONS /path HTTP/1.1
Host: 192.168.15.109:8080
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:12.0) Gecko/20100101 Firefox/12.0 FirePHP/0.7.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Origin: http://localhost
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
x-insight: activate
Pragma: no-cache
Cache-Control: no-cache
And the Response Headers:
HTTP/1.1 204 No Content
Date: Thu, 24 May 2012 19:17:01 GMT
Allow: OPTIONS,POST
As you can see, the headers doesnt match with the ones im specifying, but when i use CURL the Headers are this ones instead:
POST /path HTTP/1.1
User-Agent: curl/7.25.0 (i386-pc-win32) libcurl/7.25.0 OpenSSL/0.9.8u zlib/1.2
Host: localhost:8080
Accept: */*
Content-Type: application/json
Content-Length: 5
Any idea or solution for this?
I also modified JQuery Source to set default values of the Headers sent by Ajax to JSON, but didnt work.
Seems like a same-origin policy issue. Using dataType='jsonp' should work, but this might require other changes.
See https://developer.mozilla.org/en/http_access_control for an in-depth explanation.
Actually, it was a cross Domain problem, I defined my URL as an IP, so the browser interpreted it like a Cross Domain request.
Thanks for everything!

Resources