Confused about how to handle CORS OPTIONS preflight requests - angularjs

I'm new to working with Cross Origin Resource Sharing and trying to get my webapp to respond to CORS requests. My webapp is a Spring 3.2 app running on Tomcat 7.0.42.
In my webapp's web.xml, I have enabled the Tomcat CORS filter:
<!-- Enable CORS (cross origin resource sharing) -->
<!-- http://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#CORS_Filter -->
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
My client (written with AngularJS 1.2.12) is trying to access a REST endpoint with Basic Authentication enabled. When it makes it's GET request, Chrome is first preflighting the request, but is receiving a 403 Forbidden response from the server:
Request URL:http://dev.mydomain.com/joeV2/users/listUsers
Request Method:OPTIONS
Status Code:403 Forbidden
Request Headers:
OPTIONS /joeV2/users/listUsers HTTP/1.1
Host: dev.mydomain.com
Connection: keep-alive
Cache-Control: max-age=0
Access-Control-Request-Method: GET
Origin: http://localhost:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.107 Safari/537.36
Access-Control-Request-Headers: accept, authorization
Accept: */*
Referer: http://localhost:8000/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Response Headers:
HTTP/1.1 403 Forbidden
Date: Sat, 15 Feb 2014 02:16:05 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 0
Connection: close
I'm not entirely sure how to proceed. The Tomcat filter, by default, accepts the OPTIONS header to access the resource.
The problem, I believe, is that my resource (the request URL) http://dev.mydomain.com/joeV2/users/listUsers is configured to only accept GET methods:
#RequestMapping( method=RequestMethod.GET, value="listUsers", produces=MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public List<User> list(){
return userService.findAllUsers();
}
Does this mean that I must make that method/endpoint accept OPTIONS method as well? If so, does that mean I have to explicitly make every REST endpoint accept the OPTIONS method? Apart from cluttering code, I'm confused how that would even work. From what I understand the OPTIONS preflight is for the browser to validate that the browser should have access to the specified resource. Which I understand to mean that my controller method should not even be called during the preflight. So specifying OPTIONS as an accepted method would be counter-productive.
Should Tomcat be responding to the OPTIONS request directly without even accessing my code? If so, is there something missing in my configuration?

I sat down and debugged through the org.apache.catalina.filters.CorsFilter to figure out why the request was being forbidden. Hopefully this can help someone out in the future.
According to the W3 CORS Spec Section 6.2 Preflight Requests, the preflight must reject the request if any header submitted does not match the allowed headers.
The default configuration for the CorsFilter cors.allowed.headers (as is yours) does not include the Authorization header that is submitted with the request.
I updated the cors.allowed.headers filter setting to accept the authorization header and the preflight request is now successful.
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.headers</param-name>
<param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization</param-value>
</init-param>
</filter>
Of course, I'm not sure why the authorization header is not by default allowed by the CORS filter.

The first thing I would try is to set your common headers for your http requests that angular dispatches, by inserting the following config block on your module:
.config(function($httpProvider){
$httpProvider.defaults.headers.common = {};
$httpProvider.defaults.headers.post = {};
$httpProvider.defaults.headers.put = {};
$httpProvider.defaults.headers.patch = {};
})
These are supposed to be set by default already, but I've found that I often have to do this manually in my config due to any overrides from other modules or internal angular bootstraping process.
Your CORS filter should be enough on the server side to allow these types of requests, but sometimes, you need to specify request methods in addition to your origins, as well as accepted content types. The tomcat docs have this advanced block, which addresses those.
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.headers</param-name>
<param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
</init-param>
<init-param>
<param-name>cors.exposed.headers</param-name>
<param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
</init-param>
<init-param>
<param-name>cors.support.credentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>cors.preflight.maxage</param-name>
<param-value>10</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
If the first doesn't work on it's own, try enhancing your filters, especially:
<init-param>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.headers</param-name>
<param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
</init-param>
<init-param>
<param-name>cors.exposed.headers</param-name>
<param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
</init-param>

Related

Angular JS - Rest API - Authorization sent from browser but not received in server

I have a spring boot application with basic spring security enabled. And I have a web application written in Angular JS
When I make a rest call, I can clearly see the Authorization header being passed. But in the server, it displays the header as Null.
P.S This happens only in my machine ( yeh the same old story ) but appears to be working everywhere else.
Request Header
Accept: application/json, text/plain, */*
Authorization: Basic YWRtaW5Ac211LmVkdS5zZzpleUXXXXXXXXXXXSmhaRzFwYmtCemJYVXVaV1IxTG5ObkXXXXXXXXXXXXTmpReGZRLnXXXXXXOExJNF80MjBjMTZMUTFWX2JLR1p1VjM5SmZ3dllXbkxVTmc4LWhEeGJhdXhlMjljc3l5dWVka0w=
Content-Type: application/json
Origin: http://localhost:9000
Referer: http://localhost:9000/
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
Response Header
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials
Connection: Keep-Alive
Content-Length: 68
Content-Type: application/json;charset=UTF-8
Date: Fri, 22 Mar 2019 05:38:23 GMT
Server: NA
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Server Copmonent
The server component is built using camel and maven restlet.
from(reslet://routeName)
.log("${in.headers}"; // the auth header is completely ignored.
The app is deployed in tomcat and the web.xml has filters as below
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,PUT,DELETE</param-value>
</init-param>
<init-param>
<param-name>cors.allowed.headers</param-name>
<param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization, Cache-Control</param-value>
</init-param>
<init-param>
<param-name>cors.exposed.headers</param-name>
<param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
</init-param>
<init-param>
<param-name>cors.preflight.maxage</param-name>
<param-value>10</param-value>
</init-param>
</filter>
<filter>
<filter-name>httpHeaderSecurity</filter-name>
<filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter>
<filter-name>envHttpHeaders</filter-name>
<filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>envHttpHeaders</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>httpHeaderSecurity</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Am also behind a Corporate proxy. Does it rip off Auth header
The solution is the last line in my question
Am also behind a Corporate proxy. Does it rip off Auth header
After testing the same application in a personal computer with open internet connection, am convinced the, the corporate proxy is actually removing few headers for security reasons.

Cross-Origin Request Blocked with CORS headers present

on the server side I have the following filter in apache which allow all methods and all origins by defaults
<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Using angular $http One post is working, but another fail failed. The request that fails talks to another app on the same apache.
Cross-Origin Request Blocked: The Same Origin Policy disallows
reading the remote resource at http://localhost:..
(Reason: CORS header 'Access-Control-Allow-Origin'
does not match 'http://localhost:8100, http://localhost:8100').
But the response header does contain the ACAO
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
access-control-allow-credentials: true, true
access-control-allow-origin: http://localhost:8100, http://localhost:8100
Vary: Origin
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 22 Oct 2015 04:35:29 GMT
Where did the ' http://localhost:8100, http://localhost:8100' come from ? Do you think it is angular $http or Apache problem ?
Access-Control-Allow-Origin accepts either '*' or a single origin for its value. You can't put a comma-separated list there.
The browser is matching the origin (http://localhost:8100 against http://localhost:8100, http://localhost:8100 and not getting a match.
You have a similar problem on the line before. It looks like you are running the code to insert your CORS headers twice.

Angular JS, Jetty CORS issue

I've been ripping my hair out over this issue. Im attempting to enable CORS between an Angular App and a Jersey Server. Basically I've implemented a filter for jersey that should allow angular access to the server:
public class SimpleCORSFilter implements ContainerResponseFilter {
/**
* Add the cross domain data to the output if needed
*
* #param creq The container request (input)
* #param cres The container request (output)
* #return The output request with cross domain if needed
*/
#Override
public ContainerResponse filter(ContainerRequest creq, ContainerResponse cres) {
cres.getHttpHeaders().add("Access-Control-Allow-Origin", "*");
cres.getHttpHeaders().add("Access-Control-Allow-Headers", "origin, content-type, accept, authorization");
cres.getHttpHeaders().add("Access-Control-Allow-Credentials", "true");
cres.getHttpHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
cres.getHttpHeaders().add("Access-Control-Max-Age", "1209600");
return cres;
}
}
I've then added this as an init param to my web.xml.
<init-param>
<param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name>
<param-value>com.complaints.security.SimpleCORSFilter</param-value>
</init-param>
When I hit the URL - mysite:8080/ComplaintService/service/user/authenticate from postman with a post containing user details it returns fine (as is expected from postman) and the header contains all the correct CORS related headers. When I try to make the call from angular for some reason none of the CORS headers are being returned and it fails. Even if I just hit the URL from the browser as a GET the CORS headers are being returned. This is my angular call:
$http.post('http://mysite:8080/ComplaintService/service/user/authenticate', user).then(function(data) {
alert(JSON.stringify(data));
});
Basically I'm just wondering am I missing something simple? Or perhaps could it be related to the spring security filter? I was thinking the application could potentially be blocked from reaching the jersey filter. Here's the spring filter:
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Angular is currently deployed on jersey - mysite:8081 and the server is deployed on mysite:8080. Any help would be greatly appreciated!
I overcame this issue using a library - com.thetransactioncompany:cors-filter:1.3.2 and adding some config to my web.xml.
<filter>
<filter-name>CORS</filter-name>
<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
<init-param>
<param-name>cors.allowOrigin</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.supportedHeaders</param-name>
<param-value>origin, content-type, accept</param-value>
</init-param>
<init-param>
<param-name>cors.allowGenericHttpRequests</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>cors.supportedMethods</param-name>
<param-value>GET, POST, HEAD, PUT, DELETE</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
I think the issue was because the spring security filter was being called first, this seemed to resolve the issue.

Post document to SOLR 4 from AngularJS

I'm deep in the rabbit hole with this. I'm creating a simple app that uses SOLR 4 as a NoSQL datastore and AngularJS v1.2.2 for the interface. I've loaded a bunch of documents from the command line and AngularJS makes it very easy to search/view these. I want to permit document editing but can't get the POST working. Chrome console shows 400 errors and the Network tab shows it's failing on OPTIONS method.
Network Headers:
Request URL:http://localhost:8983/solr/mrm_assay/update
Request Method:OPTIONS
Status Code:400 Bad Request
Request Headers
Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8,es;q=0.6
Access-Control-Request-Headers:accept, content-type
Access-Control-Request-Method:POST
Cache-Control:no-cache
Connection:keep-alive
Host:localhost:8983
Origin:http://localhost:63342
Pragma:no-cache
Referer:http://localhost:63342/angular-solr2/app/index.html
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36
Response Headers
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:origin, content-type, cache-control, accept, options
Access-Control-Allow-Methods:GET,POST,DELETE,PUT,HEAD,OPTIONS
Access-Control-Allow-Origin:http://localhost:63342
Access-Control-Max-Age:1800
Content-Type:application/xml; charset=UTF-8
Transfer-Encoding:chunked
Quick overview of architecture:
Both SOLR and AngularJS apps are running on my Mac.
SOLR is using the default Jetty instance and the Angular app runs within the server from WebStorm's IDE. CORS is enabled and includes GET,POST,DELETE,PUT,HEAD,OPTIONS.
Updates work when:
Using SOLR's dashboard
Using command line (example)
$ curl http://localhost:8983/solr/mrm_assay/update -H 'Content-type:application/json' -d '[ {
"keep_in_assay" {"set" : "N",
"detected_endog": "N",
"problems_w_is": "removed b/c requires addition post-Oasis",
"onc_std_set": "set1",
"fraction_id": "7",
"onclistgene": "UBL3",
"geneid": "UBL3_IS6",
"taxonid": "9606",
"peptide": "SSNVPADMINLR",
"degen_human": "1",
"gene_degeneracy": "1",
"percnt_id": "66.7",
"uuid": "6d20eb03-d3ee-4eb2-bc16-27cfaabab989"
} ]'
My Angular controller code looks like this:
assayControllers.controller('AssayUpdateController', ['$scope', '$http',
function($scope, $http){
$scope.processForm = function(){
console.log($scope.assay);
$http({
method:'POST',
url:'http://localhost:8983/solr/mrm_assay/update',
data : $scope.assay,
headers : {'Content-Type': 'application/json' }
})
.success(function(data, status, headers){
console.log(data.message);
})
.error(function(data, status, headers, config){
console.log(status);
});
};
}
]);
Data is successfully sent from the form as I can see it on the console (although the JSON object isn't packaged in an array, which SOLR seems to expect...I also tried to push JSON-formatted data to an array and POST that but no luck)
I appreciate your help - even if it's just to direct my troubleshooting!
The answer on how to update an individual document to SOLR v4.7 using AngularJS v1.2.2 is multi-part. This has only been tested on my localhost!
I. Configure CORS on Jetty server that ships with SOLRsolr-4.7.0/example/solr-webapp/webapp/WEB-INF/web.xml
Notes: CrossOriginFilter has a parameter, chainPreflight, that is set to true by default. This needs to be set to false. This was the key to CORS forwarding POST instead of OPTIONS to SOLR.. Also, order matters!
<filter>
<filter-name>cross-origin</filter-name>
<filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
<init-param>
<param-name>allowedOrigins</param-name>
<param-value>http://localhost*</param-value>
</init-param>
<init-param>
<param-name>allowedMethods</param-name>
<param-value>GET,POST,DELETE,PUT,HEAD,OPTIONS</param-value>
</init-param>
<init-param>
<param-name>allowedHeaders</param-name>
<param-value>origin, content-type, cache-control, accept, options, authorization, x-requested-with</param-value>
</init-param>
<init-param>
<param-name>supportsCredentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>chainPreflight</param-name>
<param-value>false</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>cross-origin</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
II. SOLR expects payload in array format, so you need to push Angular object into one. Basically, $scope.data becomes [$scope.data]
assayControllers.controller('AssayUpdateController', ['$scope', '$http', '$location',
function($scope, $http, $location){
$scope.processForm = function(){
$http.post("http://localhost:8983/solr/mrm_assay/update?commit=true", [$scope.assay])
.success(function(data, status, headers){
console.log(data.message);
$location.path("/assays")
})
.error(function(data, status, headers, config){
console.log(status);
});
};
}
]);
III. SOLR documents contain version field that can cause "conflict" errors on POST. This is due to a cache issue I wasn't able to track down (curl showed current value but browsers had old one; even on force refresh). Anyway, this is of more use to SOLR than me so the best bet is to not return it to the client in the first place. I don't think field exclusion is an option so I whitelisted all the fields I wanted to return in solrconfig.xml
<!-- A request handler that returns indented JSON by default -->
<requestHandler name="/query" class="solr.SearchHandler">
<lst name="defaults">
<str name="echoParams">explicit</str>
<str name="wt">json</str>
<str name="indent">true</str>
<str name="df">text</str>
<str name="fl">uuid,keep_in_assay,detected_endog,problems_w_is,onc_std_set,fraction_id,detected_by_orbi_experiments,onclistgene,geneid,taxonid,peptide,\
degen_human,degen_mouse,gene_degeneracy,department,protein_ac,pepid,protid,percnt_id,peptide_ordered</str>
Now the app works like a charm!
If you are failing on OPTIONS request then it is CORS that's causing problems. Most likely because the jetty that comes with Solr does not support (or is not configured) with one. The latest version of Jetty has one, but it still needs to be configured.
So, you can disable CORS on Solr requests or enable it in Jetty. Upgrading Jetty if the version shipped does not support it.
However, you also have a second problem. Solr should not be exposed directly to the internet but is supposed to run behind a middleware client or - worst case - a heavily restricting proxy. Otherwise, anybody can connect directly to your instance and delete the data or do other damage. Solr is not even security tested for direct internet access. So keep that in mind before you get too far down 'Solr-Angular' direct bridge.

"HTTP ERROR: 500 No realm" running GWT's "MobileWebApp" sample

I'm trying to run the GWT 2.4 sample app "MobileWebApp". I get a 500 "No Realm" error when I try to run the app in dev mode through Eclipse.
I understand this is an authentication problem.
I'm not familiar with Google App Engine or Jetty but from looking at the web.xml I can see there is a servlet filter where it is using the appengine UserService to presumably redirect the user to Google for authentication.
I'm using:
Eclipse 3.7 (Indigo SR1)
Google Plugin for Eclipse 2.4
m2eclipse
I'm including an excerpt from the web.xml below. I'm not sure what other info would be helpful in diagnosing this problem.
<security-constraint>
<display-name>
Redirect to the login page if needed before showing
the host html page.
</display-name>
<web-resource-collection>
<web-resource-name>Login required</web-resource-name>
<url-pattern>/MobileWebApp.html</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
<filter>
<filter-name>GaeAuthFilter</filter-name>
<!--
This filter demonstrates making GAE authentication
services visible to a RequestFactory client.
-->
<filter-class>com.google.gwt.sample.gaerequest.server.GaeAuthFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>GaeAuthFilter</filter-name>
<url-pattern>/gwtRequest/*</url-pattern>
</filter-mapping>
Below is the output in the Eclipse console:
[WARN] Request /MobileWebApp.html failed - no realm
[ERROR] 500 - GET /MobileWebApp.html?gwt.codesvr=127.0.0.1:9997 (127.0.0.1) 1401 bytes
Request headers
Host: 127.0.0.1:8888
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:7.0.1) Gecko/20100101 Firefox/7.0.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
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Response headers
Content-Type: text/html; charset=iso-8859-1
Content-Length: 1401
Many thanks for any helpful advice!
Edit on 11/11/11: I added Jetty tag since it seems relevant to this problem.
If your very first request fails, just getting the /MobileWebApp.html page, then it probably isn't an authentication problem. Do you have GAE enabled for that project (not only GWT)? That might be one issue.
I read somewhere that there's two ways of debugging an app in Eclipse, one is with run as/webapp, and forgot which was the other one (I don't use Eclipse). One of them works and another doesn't.
If that doesn't work, you can try replacing the built-in jetty:
add a GWT param: -server com.google.appengine.tools.development.gwt.AppEngineLauncher
VM param: -javaagent:/path_to/appengine-agent.jar
And the last option is with -noserver, but then you wont be able to debug the server-side code, just the client-side GWT stuff: first start jetty with mvn jetty:run and then debug in Eclipse with -noserver GWT param.
I had the same problem. Finally I noticed that when I switched to a newer version of Appengine, the older Appengine libraries remained in the WEB-INF/lib along with the new ones.
Removing them solved the problem.

Resources