CORS preflight issues in Firefox and Chrome

1
0
-1

We are running into an issue making a Cross Origin call to the Bonita API. The issue we are having is similar to the problem described in the linked post but the solution to that question is not fixing the problem in our current environment: http://community.bonitasoft.com/answers/cors-ajax-bonita-rest-ap-and-tom...

What I am seeing is when I make a call that causes a preflight (OPTIONS) request it is failing with a 401 Unauthorized response in Firefox. It is working in Chrome; however, I believe that is because of an issue in Chrome and Firefox is actually doing what it is supposed to do which causes the failure (this should make more sense as you read on).

Here is the request in Chrome. Notice the cookie:

OPTIONS /bonita/API/bpm/humanTask?p=0&c=100&f=state=ready HTTP/1.1 Host: X.X.X.X:8080 Connection: keep-alive Access-Control-Request-Method: GET Origin: http://myworkstation.company.com:8082 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36 Access-Control-Request-Headers: accept, content-type Accept: / Referer: http://myworkstation.company.com:8082/index.html Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Cookie: JSESSIONID=EF06ABFF189A89F8D95791F44BD7B8C3; BOS_Locale=en

And here is the response in Chrome: HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Access-Control-Allow-Origin: http://myworkstation.company.com:8082 Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET, HEAD, OPTIONS, POST, DELETE, PUT Access-Control-Allow-Headers: Content-Type, Accept Content-Length: 0 Date: Wed, 02 Jul 2014 12:45:24 GMT

Here is the request in Firefox. No cookie is passed: OPTIONS /bonita/API/bpm/humanTask?p=0&c=100&f=state=ready HTTP/1.1 Host: X.X.X.X:8080 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:30.0) Gecko/20100101 Firefox/30.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://myworkstation.company.com:8082 Access-Control-Request-Method: GET Access-Control-Request-Headers: content-type Connection: keep-alive

Here is the response in Firefox: HTTP/1.1 401 Unauthorized Server: Apache-Coyote/1.1 Set-Cookie: JSESSIONID=DC1E9B22973D2EF3EBB587E11BCD03BC; Path=/bonita/; HttpOnly Content-Type: text/html;charset=utf-8 Content-Length: 951 Date: Wed, 02 Jul 2014 12:40:49 GMT

I believe the reason this is working in Chrome is because Chrome is passing the Cookie; however, according to w3.org, a CORS preflight request shouldn't expect a cookie; which makes sense. A preflight call is a call to determine if an action is allowed. It should not require credentials to determine if I can do something, it should only require credentials to actually do it.

This is similar to an issue seen in the Twitter API which may explain the problem better than I am: https://code.google.com/p/twitter-api/issues/detail?id=2273

Finally, I realize this is a GET request and the only reason that it is causing a preflight is because I am setting the Content-Type header to application/json. I could fix this particular instance of the issue by removing the Content-Type on the GET; however, I am still making post calls at some point and if I do not specify the Content-Type as application/json on my posts (i.e. the API/bpm/case call in order to start a case, see example below). Setting application/json as the Content-Type will always cause a preflight request so I do not see a way to avoid the issue.

var startCase = function startCase(processId){ return $.ajax({ xhrFields: {withCredentials: true}, contentType: "application/json", //Required or you get a 500 error url: bonitaUrl + "API/bpm/case/", type: 'POST', data: '{"processDefinitionId":"' + processId + '"}' }); }

This can be solved (from what I can tell) if Bonita changes the API so that a preflight (OPTIONS) request does not require authorization.

Comments

Submitted by ryan.dever on Fri, 07/18/2014 - 14:30

Some additional information may be helpful.

I am running the ajax code locally on my workstation.

I am connecting to a subscriber standalone Bonita instance. The Bonita version is 6.2.3. The tomcat version is 7.0.33.

I am using a third party libraries to enable the CORS filter in tomcat. The website to download the libraries is http://software.dzhuvinov.com/cors-filter-installation.html

Inside of /usr/share/tomcat/conf/web.xml I have the CORS filter setup as follows:

  1. <filter>
  2. <filter-name>CORS</filter-name>
  3. <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
  4.  
  5. <init-param>
  6. <param-name>cors.allowOrigin</param-name>
  7. <param-value>http://myworkstation.company.com:8082, http://otherworkstation.company.com:8082</param-value>
  8. </init-param>
  9.  
  10. <init-param>
  11. <param-name>cors.supportedMethods</param-name>
  12. <param-value>GET, HEAD, POST, PUT, DELETE, OPTIONS</param-value>
  13. </init-param>
  14.  
  15. <init-param>
  16. <param-name>cors.supportedHeaders</param-name>
  17. <param-value>Accept, accept, Content-Type, content-type</param-value>
  18. </init-param>
  19. </filter>

I have commented out this line in /usr/share/tomcat/conf/server.xml

  1. <Valve className="org.bonitasoft.console.security.SessionFixationValve" authenticationUrl="/loginservice"/>
Submitted by ryan.dever on Wed, 10/22/2014 - 16:46

Update: This no longer works in the latest version of Chrome either. I assume that the bug in Chrome that was allowing this to work has been patched.

1 answer

1
0
-1

This is a real dealbreaker: the set-cookie is httpOnly, how are we supposed to read it to set it subsequent ajax calls?

Comments

Submitted by ryan.dever on Thu, 12/04/2014 - 14:32

You don't need to set the cookie directly. You chain your calls. Here are some example functions from a proof of concept site that I wrote:

  1. var bonitaUrl = "http://localhost:8080/bonita/";
  2.  
  3. var loginBonita = function loginBonita(){
  4. return $.ajax({
  5. xhrFields: {withCredentials: true},
  6. contentType: "application/x-www-form-urlencoded",
  7. url: bonitaUrl + "loginservice",
  8. type: 'POST',
  9. data: 'username=walter.bates&password=bpm&redirect=false'
  10. });
  11. };
  12.  
  13. var logoutBonita = function logoutBonita(){
  14. return $.ajax({
  15. xhrFields: {withCredentials: true},
  16. url: bonitaUrl + "logoutservice?redirect=false",
  17. type: 'GET',
  18. });
  19. };
  20.  
  21. var getHumanTasks = function humanTasks(){
  22. return $.ajax({
  23. xhrFields: {withCredentials: true},
  24. url: bonitaUrl + "API/bpm/humanTask?p=0&c=100&f=state=ready",
  25. type: 'GET'
  26. });
  27. };
  28.  
  29. var getProcesses = function getProcesses(){
  30. return $.ajax({
  31. xhrFields: {withCredentials: true},
  32. url: bonitaUrl + "API/bpm/process?p=0&c=100",
  33. type: 'GET'
  34. });
  35. };
  36.  
  37. var getUsers = function getUsers(){
  38. return $.ajax({
  39. xhrFields: {withCredentials: true},
  40. url: bonitaUrl + "API/identity/user?p=0&c=100",
  41. type: 'GET'
  42. });
  43. };

To get the human tasks (and a little more information) I would do something like this:

  1. var getRunningTasks = function() {
  2. // We need to get a list of tasks from Bonita. The tasks list returns the process id and a user id (if the task is assigned).
  3. // We want to display the process name and the assigned user name so we need to make additional calls to get that data.
  4. // Since this is asyncronous code we need to gather the data up and then put it all together.
  5. $.when(loginBonita()).done(function(l){
  6. // When all calls are done put things together...
  7. $.when(getHumanTasks(), getProcesses(), getUsers()).done(function(t, p, u){
  8. //When the human task call is done.
  9. logoutBonita(); // Logout. No $.when call here because we don't care how log it takes to log out.
  10. var processName = ""; // Variable to store the current process name.
  11. var assigneeUserName = "" // Variable to hold the current assignee user name.
  12. //Loop through all the processes from Bonita.
  13. for(var i = 0; i < t[0].length; i++) {
  14. // Simple loop to get the current process name. We aren't dealing with a lot of data
  15. // so we don't need anything fancy.
  16. processName = "Unknown"; // Set to a default value in case is isn't found (which should never happen here).
  17. for (var j = 0; j < p[0].length; j++){
  18. if (p[0][j]["id"] == t[0][i]["processId"]){
  19. processName = p[0][j]["name"]
  20. break; // We found what we needed. Stop looking...
  21. }
  22. }
  23. // Simple loop to get the current assignee user name. We aren't dealing with a lot of data
  24. // so we don't need anything fancy.
  25. assigneeUserName = "Unassigned"; // Set to a default value in case it isn't found (which might happen here).
  26. for (var j = 0; j < u[0].length; j++){
  27. if (u[0][j]["id"] == t[0][i]["assigned_id"]){
  28. assigneeUserName = u[0][j]["userName"];
  29. }
  30. }
  31. // Add the data to the processes table.
  32. $('#processes tbody').append('<tr><td><a href="javascript:void(0);" onclick="javascript:BonitaPOC.switchToWorkOrder(' +
  33. t[0][i]["caseId"] +');"</a>' + t[0][i]["caseId"] + '</td><td>' + processName + '</td><td>'+ t[0][i]["displayName"] +
  34. '</td><td>' + assigneeUserName + '</td></tr>');
  35. }
  36. })
  37. });
  38. };

The problem I am having is that the Bonita API seems to expect the credentials to be passed on a pre-flight (OPTIONS) call most browsers don't pass (and shouldn't from what I can tell). I only see this when running on a remote server (or any other configuration that forces a CORS call). I do not know if this is a problem with the latest version of Bonita. I ended up creating a facade class using Ruby and Sinatra for the pieces of the Bonita REST API that I use so I wouldn't need to worry about it anymore.

Notifications