Ember-data doesn't support nested API urls in models. For that we need to write our own custom adapter. I have added the nested_url_adapter .
The issues that I am having right now::
When doing a POST request to the api "http://api.server/resource/resourceId/childResource",
the request payload on the request headers is not present.
2̶.̶ ̶I̶ ̶a̶m̶ ̶u̶s̶i̶n̶g̶ ̶e̶m̶b̶e̶r̶-̶s̶i̶m̶p̶l̶e̶-̶a̶u̶t̶h̶ ̶l̶i̶b̶ ̶f̶o̶r̶ ̶t̶h̶e̶ ̶a̶u̶t̶h̶e̶n̶t̶i̶c̶a̶t̶i̶o̶n̶ ̶a̶n̶d̶ ̶a̶u̶t̶h̶o̶r̶i̶z̶a̶t̶i̶o̶n̶,̶ ̶w̶h̶i̶l̶e̶ ̶d̶o̶i̶n̶g̶ ̶t̶h̶e̶ ̶P̶O̶S̶T̶ ̶r̶e̶q̶u̶e̶s̶t̶,̶ ̶i̶n̶ ̶t̶h̶e̶ ̶r̶e̶q̶u̶e̶s̶t̶ ̶h̶e̶a̶d̶e̶r̶ ̶t̶h̶e̶r̶e̶ ̶i̶s̶ ̶n̶o̶ ̶A̶u̶t̶h̶o̶r̶i̶z̶a̶t̶i̶o̶n̶ ̶h̶e̶a̶d̶e̶r̶.̶
I have implemented the same nested_url adapter with little modification to the type.typeKey as per my api server.
Here are the gist of the files that I have in my application:
Gist
XHR request tabs::
Remote Address: 127.0.1.1: 80
Request URL: http: //api.server/events/9/tickets
Request Method: OPTIONS
Status Code: 200 OK
Request Headersview parsed
OPTIONS / events / 9 / tickets HTTP / 1.1
Host: api.server
Connection: keep - alive
Access - Control - Request - Method: POST
Origin: http: //0.0.0.0:4300
User - Agent: Mozilla / 5.0(X11; Linux x86_64) AppleWebKit / 537.36(KHTML, like Gecko) Chrome / 36.0.1941.0 Safari / 537.36
Access - Control - Request - Headers: accept,
client_id
Accept: *
/*
Referer: http://0.0.0.0:4300/event/9/manage/tickets
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Response Headersview parsed
HTTP/1.1 200 OK
Date: Thu, 24 Apr 2014 11:51:07 GMT
Server: Apache/2.4.9 (Ubuntu)
X-Powered-By: PHP/5.5.11-2+deb.sury.org~precise+2
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, client_id, client_secret
Access-Control-Max-Age: 0
Content-Length: 0
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html*/
POST request ::
Remote Address: 127.0.1.1: 80
Request URL: http: //api.server/events/9/tickets
Request Method: POST
Status Code: 201 Created
Request Headersview source
Accept: application / json,
text / javascript,
*
/*; q=0.01
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
client_id:[object Object]
Connection:keep-alive
Content-Length:0
Host:api.server
Origin:http://0.0.0.0:4300
Referer:http://0.0.0.0:4300/event/9/manage/tickets
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1941.0 Safari/537.36
Response Headersview source
Access-Control-Allow-Origin:*
Access-Control-Expose-Headers:
Connection:Keep-Alive
Content-Length:242
Content-Type:application/hal+json
Date:Thu, 24 Apr 2014 11:51:07 GMT
Keep-Alive:timeout=5, max=99
Location:http://api.server/events/9/tickets/24
Server:Apache/2.4.9 (Ubuntu)
X-Powered-By:PHP/5.5.11-2+deb.sury.org~precise+2*/
As per marcoow's comment the 2nd issue has been solved. Still the 1st issue is there.
When you're using Ember.SimpleAuth with cross-origin requests you have to explicitly whitelist these origins to have the Authorization header added by Ember.SimpleAuth (that's a security feature to make sure your token doesn't get published all across the internet) - see here: http://ember-simple-auth.simplabs.com/ember-simple-auth-api-docs.html#Ember-SimpleAuth-setup
Related
I found HTTP POST request in website the request body and the response body encrypted and the Content-Type: = application/encrypted-json;charset=UTF-8
i don't understand what is this? it's first time to see HTTP request like this! so i tried to search on google about HTTP json encryption or Content-Type: application/encrypted-json and i searched in stackoverflow, but i couldn't find anything
The request
POST /api/login/v1/auth HTTP/1.1
Host: id.example.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: application/encrypted-json;charset=UTF-8
Accept-Language: en-US
Accept-Encoding: gzip, deflate
X-BusinessSystem: example
X-Timezone: GMT+2
X-From-HT: false
Content-Type: application/encrypted-json;charset=UTF-8
X-Protocol-Version: 1.0
X-Key: Xb14CswCy5e8XQPLb8HG0JbPc4QxZw+3t877CxyHJNCPg5OJpHR7C2X2/a240JEHDGnb2cdMBR1huNHlpywFbCxKYp6N3axdymn5HcgfjmBQlHienlXFqTs1g+J0nukGSAjzKMy1YsYf9oceIyM17wiUcIGeLK9J250X1TSN1ss=
X-Session-Ticket: qWGUmMh1c6EwWhuVArLoxWYMSs6pEi4c341Fz3ANiKw=
Content-Length: 280
Cookie: pgv_pvid=9688842132; __qc_wId=663
Connection: close
vEsDAG4HIvLzuxqJlppxeAUeURzkuA0VclgnhAglRHF/xQ0lJ7rReK2BR264OzOONgY/lGHtcWXt7AAqxFplzqI7kocCghJsR/nQtqRjn4ee6LwbBVaSZOQpWPpnuXifU8iTyCe3Akb32DESTHuZHN4S0cEDg8xYFkf/8ZlTLkTDcfbit0BtpDovEGIa9RM7Q7E12Bm/lMbohwdKCfvHCzxOwkuWpthOOp3ycdVIPeDRcryrczSnqu29o32PdCKrVSsoNQbTUfR4yzgoQPb9Fg==
The response:
HTTP/1.1 200 OK
Date: Tue, 23 Jun 2020 19:24:26 GMT
Content-Type: application/encrypted-json;charset=UTF-8
Content-Length: 108
Connection: close
Server: nginx
X-Frame-Options: ALLOW-FROM https://id.example.com
X-Session-Ticket: qWGUmMh1c6EwWhuVArLoxWYMSs6pEi4c341Fz3ANiKw=
X-Gateway-Host: a499fa989cb43158986efe1417998cec3f49ef0cb03627023ff6a5beac0b05908b28c318e96f6dfe6387f4617d46734f
X-Backend-Host: 0162:18090
6iPusB8aZSx5Bf8PHHlWLg4Ui0FtF/q9zCbDz2W1M7AGj1k/CSN2zrRYZTp7Z+5lwjpRfYMuWrskAx9vgKHH6VS+Ma/vccl1X0+Ki2IRFxA=
I tried to read the js files, i found something about the encryption, but i couldn't understand anything!
("HttpEncrypt",this,function(){"use strict";var e=n("a2ec"),t=n("2324"),r=n("cd45"),i=n("b3a4"),o=n("509f").default,a=new o,s="",u=function(e){if(!(this instanceof u))return new u(e);e=e||{},this.rsaPublicKey=e.rsaPublicKey||"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDpgSW5VkZ6/xvh+wMXezrOokNdiupuvuMj4RVJy44byWDupl4H37z907A26RVdFzMeyLUQB4rsDIaXdxCODlljWW+/K96uF5MsDtOFUBw7VlOclIjcYTv/YDQEul8JoXoOuy1Yf3b5sbTpTuVTcl97tAuLJ8PoGe2K7N3B1eUQqQIDAQAB",this.protocolVersion=e.protocolVersion||"1.0",this.XSecurity=e.XSecurity||"",this.retryCount=0};u.prototype={getAesPrivateKey:function(){try{var e=this.getSessionAesKey()||""}catch(t){e=""}return e||this.createAESTicket()},getXKey:function(){return this.createAESTicket()},getSecurityTicket:function(){try{var e=window.sessionStorage.getItem("securitySessionTicket")||""}catch(t){e=""}return e},getSessionAesKey:function(){try{var e=window.sessionStorage.getItem("securitySessionTicket")||""}catch(n){e=""}var t="";if(e)try{t=window.sessionStorage.getItem("securitySessionAesTicket")||""}catch(n){}return t},createAESTicket:function(){if(s)return s;for(var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789",t=e.length,n="",r=0;r<16;r++)n+=e.charAt(Math.floor(Math.random()*t));return s=n,n},encrypt:function(n,o,s,u){return s=s||{},u=u||"AES",u&&"RSA"===u.toUpperCase()?(a.setPublicKey(o),a.encrypt(n)):(s.mode=s.mode||r,s.padding=s.padding||i,e.encrypt(t.parse(n),t.parse(o),s).toString())},decrypt:function(n,o,s,u){return s=s||{},u=u||"AES","RSA"===u.toUpperCase()?(a.setPublicKey(o),a.decrypt(n)):(s.mode=s.mode||r,s.padding=s.padding||i,e.decrypt(n,t.parse(o),s).toString(t))},encryptKey:function(n){if(!n)return"";var o=this.getAesPrivateKey();return e.encrypt(t.parse(n),t.parse(o),{mode:r,padding:i}).toString()},decryptKey:function(n){if(!n)return"";var o=this.getAesPrivateKey();return e.decrypt(n,t.parse(o),{mode:r,padding:i}).toString(t)},getEncryptAesKey:function(){var e=this.createAESTicket();return a.setPublicKey(this.rsaPublicKey),a.encrypt(e)},encryptBody:function(e){var t="";return t="object"==typeof e?JSON.stringify(e):e,this.encryptKey(t)},encryptHeader:function(e,t){if(e&&e.setRequestHeader){var n=this.getEncryptAesKey(),r=this.getSecurityTicket(),i=this.getSessionAesKey();try{e.setRequestHeader("Content-Type","application/encrypted-json;charset=UTF-8")}catch(o){e.setRequestHeader("Content-Type","application/encrypted-json;charset=UTF-8")}try{e.setRequestHeader("Accept","application/encrypted-json;charset=UTF-8")}catch(o){e.setRequestHeader("Accept","application/encrypted-json;charset=UTF-8")}if("2.0"===this.protocolVersion){try{e.setRequestHeader("X-Protocol-Ver","1.0")}catch(o){e.setRequestHeader("X-Protocol-Ver",encodeURI("1.0"))}try{e.setRequestHeader("X-Protocol",encodeURIComponent(JSON.stringify({key:n,sessionTicket:r&&i?r:""})))}catch(o){e.setRequestHeader("X-Protocol",encodeURIComponent(JSON.stringify({key:n,sessionTicket:r&&i?r:""})))}}else{try{e.setRequestHeader("X-Protocol-Version",this.protocolVersion)}catch(o){e.setRequestHeader("X-Protocol-Version",encodeURI(this.protocolVersion))}try{e.setRequestHeader("X-Key",n)}catch(o){e.setRequestHeader("X-Key",encodeURI(n))}}try{r&&i&&e.setRequestHeader("X-Session-Ticket",r)}catch(o){r&&i&&e.setRequestHeader("X-Session-Ticket",encodeURI(r))}try{this.XSecurity&&e.setRequestHeader("X-Security",this.encryptKey(this.XSecurity))}catch(o){this.XSecurity&&e.setRequestHeader("X-Security",encodeURI(this.encryptKey(this.XSecurity)))}}},send:function(e,t,n,r,i,o){o=o||"post";var a=XMLHttpRequest?new XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");for(var s in a.open(o,e,!0),n=n||{},t=t||{},n)if(n.hasOwnProperty(s)&&"Ext-User"!=s&&"X-OPPO-DeviceName"!=s){var u=n[s];if("X-Safety"===s){try{a.setRequestHeader("X-Safety",this.encryptKey(u))}catch(f){a.setRequestHeader("X-Safety",encodeURIComponent(this.encryptKey(u)))}continue}if("Content-Type"===s)continue;try{a.setRequestHeader(s,u)}catch(f){a.setRequestHeader(s,encodeURI(u))}}this.encryptHeader(a);var l=this.encryptBody(t),c=this;a.onreadystatechange=function(s){if(4==a.readyState)if(200==a.status){var u,l=a.responseText||"",d=a.getResponseHeader("x-session-ticket");if(d)try{window.sessionStorage.setItem("securitySessionTicket",d),window.sessionStorage.setItem("securitySessionAesTicket",c.getAesPrivateKey())}catch(h){}try{u=JSON.parse(c.decryptKey(l))}catch(f){u=c.decryptKey(l)}r&&r(u)}else if(222==a.status){if(c.retryCount<2){c.retryCount++;try{window.sessionStorage.removeItem("securitySessionTicket"),window.sessionStorage.removeItem("securitySessionAesTicket")}catch(h){}return c.send(e,t,n,r,i,o)}c.commomSend(e,t,n,r,i,o)}else i&&i()},a.send(l)},commomSend:function(e,t,n,r,i,o){o=o||"post";var a=XMLHttpRequest?new XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");for(var s in a.open(o,e,!0),n=n||{},t=t||{},n)if(n.hasOwnProperty(s)){var u=n[s];try{a.setRequestHeader(s,u)}catch(l){a.setRequestHeader(s,encodeURI(u))}}a.onreadystatechange=function(){if(4==a.readyState)if(200==a.status){var e,t=a.responseText||"";try{e=JSON.parse(t)}catch(l){e=t}r&&r(e)}else i&&i(a.status)},a.send(JSON.stringify(t))}};try{window&&(window.HttpEncrypt=u)}catch(l){}return u})},"35c6":function(e,t,n){"use strict";var r=n("3332"),i=n("79c0"),o=n("3673"),a=n("5ce4"),s=n("2be5"),u=n("45d2");function
Can anyone please explain what is that?
and explain how to decrypt something like that?
Thanks.
I'm trying to fetch data from my Jira Cloud instance through the Jira and Jira Agile REST APIs using JavaScript in a browser. Queries to Jira REST API work fine but identical queries to Jira Agile REST API keep failing with the response
Response for preflight has invalid HTTP status code 401.
I'm using Basic Authentication with user ID and an API token obtained from Jira. With cURL and ARC, I'm able to successfully retrieve data both from the Jira REST API and the Jira Agile REST API, so the authentication against both APIs seems to work. In JS I have tried with both fetch() and jquery ajax() and the result was the same.
function fetchFromJira(url, id, token) {
const authorizationString = 'Basic ' + btoa(id + ':' + token);
const options = {
method: 'GET',
headers: {
Authorization: authorizationString,
'Content-Type': 'application/json',
},
};
fetch(url, options)
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error(response.status);
}
})
.then(json => {
console.log(json);
})
.catch(error => {
console.log(error);
});
}
fetchFromJira(
'https://fredrikastrom.atlassian.net/rest/api/latest/issue/10000',
'<user id>',
'<API token>'
); // successful
fetchFromJira(
'https://fredrikastrom.atlassian.net/rest/agile/1.0/board',
'<user id>',
'<API token>'
); // fails
The output onto the console looks as follows:
test.js:11 OPTIONS https://fredrikastrom.atlassian.net/rest/agile/1.0/board 401 ()
fetchFromJira # test.js:11
(anonymous) # test.js:33
index.html:1 Failed to load https://fredrikastrom.atlassian.net/rest/agile/1.0/board: Response for preflight has invalid HTTP status code 401.
test.js:23 TypeError: Failed to fetch
test.js:20 {expand: "renderedFields,names,schema,operations,editmeta,changelog,versionedRepresentations", id: "10000", self: "https://fredrikastrom.atlassian.net/rest/api/latest/issue/10000", key: "FAT-1", fields: {…}}
Here are the preflight request and response headers of the successful query to the Jira REST API:
t=3241 [st= 89] HTTP_TRANSACTION_HTTP2_SEND_REQUEST_HEADERS
--> :authority: fredrikastrom.atlassian.net
:method: OPTIONS
:path: /rest/api/latest/issue/10000
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: sv-SE,sv;q=0.9,en-US;q=0.8,en;q=0.7,fi;q=0.6
access-control-request-headers: authorization,content-type
access-control-request-method: GET
cache-control: no-cache
origin: http://127.0.0.1:8080
pragma: no-cache
referer: http://127.0.0.1:8080/test/index.html
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36
t=3241 [st= 89] -HTTP_TRANSACTION_SEND_REQUEST
t=3241 [st= 89] +HTTP_TRANSACTION_READ_HEADERS [dt=68]
t=3278 [st=126] HTTP2_STREAM_UPDATE_SEND_WINDOW
--> delta = 0
--> stream_id = 1
--> window_size = 65535
t=3309 [st=157] HTTP_TRANSACTION_READ_RESPONSE_HEADERS
--> HTTP/1.1 200
status: 200
server: AtlassianProxy/1.15.8.1
vary: Accept-Encoding
cache-control: no-cache, no-store, no-transform
content-type: text/html;charset=UTF-8
content-encoding: gzip
strict-transport-security: max-age=315360000; includeSubDomains; preload
date: Sat, 12 Oct 2019 06:33:50 GMT
atl-traceid: 519aa518a8e8e5ea
x-arequestid: c68d7b95-3635-49e1-a2fd-971e0502adf5
x-xss-protection: 1; mode=block
timing-allow-origin: *
x-content-type-options: nosniff
set-cookie: atlassian.xsrf.token=7a27221d-39bc-4555-9569-b26a0beb9689_b9e038120f5696c0bac7202f986ee24d3752c6fa_lout; Path=/; Secure
and here are the correspinding headers from the failing request to Jira Agile REST API:
t=5918 [st= 5] HTTP_TRANSACTION_HTTP2_SEND_REQUEST_HEADERS
--> :authority: fredrikastrom.atlassian.net
:method: OPTIONS
:path: /rest/agile/latest/board
:scheme: https
accept: */*
accept-encoding: gzip, deflate, br
accept-language: en-GB,en-US;q=0.9,en;q=0.8
access-control-request-headers: authorization,content-type
access-control-request-method: GET
origin: http://127.0.0.1:8080
referer: http://127.0.0.1:8080/test/index.html
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36
t=5919 [st= 6] -HTTP_TRANSACTION_SEND_REQUEST
t=5919 [st= 6] +HTTP_TRANSACTION_READ_HEADERS [dt=65]
t=5984 [st=71] HTTP_TRANSACTION_READ_RESPONSE_HEADERS
--> HTTP/1.1 401
status: 401
server: AtlassianProxy/1.15.8.1
vary: Accept
www-authenticate: OAuth realm="https%3A%2F%2Ffredrikastrom.atlassian.net"
cache-control: no-transform
content-type: application/xml;charset=UTF-8
strict-transport-security: max-age=315360000; includeSubDomains; preload
date: Sat, 12 Oct 2019 07:05:10 GMT
atl-traceid: 2caf28fb1cce9a77
x-arequestid: 817e2b89-e3d1-431b-b892-781fc78c9669
x-xss-protection: 1; mode=block
timing-allow-origin: *
x-content-type-options: nosniff
set-cookie: atlassian.xsrf.token=7a27221d-39bc-4555-9569-b26a0beb9689_dafc86c05dbdc472c9b99300b351fe0dd62b305d_lout; Path=/; Secure
content-length: 174
Interestingly, the request headers look slightly different even if the requests are made with the same function where only the requested URLs differ. The succeeding request includes the cache-control and pragma headers, and the accept-language header includes one more language. But none of these should reasonably have any impact on whether the server will accept the preflight request?
Any clue why the one request succeeds and the other one fails?
I'm developing an Azure MobileService / CordovaApp setup. The server runs locally and has the following setting to enable CORS:
<add name="Access-Control-Allow-Origin" value="*" />
The api can be called via browser using addresses like
http://localhost:59477/api/item/explore/A/B
The client script that's trying to depict this call is the following:
var client = new WindowsAzure.MobileServiceClient('http://localhost:59477/', 'http://localhost:59477/', '');
GetItems.addEventListener("click",
function ()
{
client.invokeApi("item/explore/A/B",
{
method: "get"
})
.done(
function (results)
{
alert(results.result.count);
},
function (error)
{
var xhr = error.request;
alert('Error - status code: ' + xhr.status + '; body: ' + xhr.responseText);
alert(error.message);
}
);
}
);
What I get is status 405 - Method not allowed, which I don't understand for two reasons:
The invoked Service Action is decorated with the [HttpGet] Attribute
The response headers seem to say GET is fine:
HTTP/1.1 405 Method Not Allowed
Allow: GET
Content-Length: 76
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-SourceFiles: =?UTF-8?B?RjpcQ29kZVxGb290UHJpbnRzXGZvb3RwcmludHMuU2VydmVyXEZvb3RwcmludHNTZXJ2aWNlXGFwaVxmb290cHJpbnRcZXhwbG9yZVw1MS4yNzcwMjJcNy4wNDA3ODM=?=
X-Powered-By: ASP.NET
Access-Control-Allow-Origin: *
Date: Sat, 15 Aug 2015 18:08:30 GMT
Any idea what I can do to get this running?
Edit
The Raw request shows that, as already expected by Jeremy Foster, the client sends a request of the type OPTIONS:
OPTIONS http://localhost:59477/api/fp/get?id=1 HTTP/1.1
Host: localhost:59477
Connection: keep-alive
Access-Control-Request-Method: GET
Origin: http://localhost:4400
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.155 Safari/537.36
Access-Control-Request-Headers: accept, x-zumo-features, x-zumo-installation-id, x-zumo-version
Accept: */*
Referer: http://localhost:4400/index.html
Accept-Encoding: gzip, deflate, sdch
Accept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
I managed to support this request applying the [HttpOptions] attribute to my action method. Now I get a more or less valid response:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-SourceFiles: =?UTF-8?B?RjpcQ29kZVxGb290UHJpbnRzXGZvb3RwcmludHMuU2VydmVyXEZvb3RwcmludHNTZXJ2aWNlXGFwaVxmcFxnZXRcMQ==?=
X-Powered-By: ASP.NET
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
Date: Sun, 23 Aug 2015 19:34:21 GMT
Content-Length: 234
...
At least that's what fiddler tells me. The AzureMobileServiceClient throws yet another issue, which roughly translates as:
Cross-Origin request blocked, missing token 'x-zumo-installation-id' in CORS header row 'Access-Control-Allow-Headers' on CORS-Preflight-Channel
Turn on Fiddler and try your api call from the browser and then again using the Mobile Services client and compare your raw HTTP requests to see what's different.
Try to run through the CORS Support section of this blog post: http://azure.microsoft.com/blog/2014/07/28/azure-mobile-services-net-updates/.
A side note: The x-zumo-installation-id error you were getting was because the client requested access for a list of headers (see the Access-Control-Request-Headers in your request) and the server did not return that same list in the Access-Control-Allow-Headers header. Using the MS_CrossDomainOrigins setting as mentioned in the above blog post takes care of this by setting the allowed headers to * (all) by default.
I am using backbone to get data from an API. This all works fine when there is no authorization required and when I add the authorization to the API, I get the expected 401 - unauthorised response.
[from the console log:
GET http://localhost:999/api/tasks 401 (Unauthorized)
]
I've then add in this code to add the bearer authorization to the header for every call:
var backboneSync = Backbone.sync;
Backbone.sync = function (method, model, options) {
/*
* The jQuery `ajax` method includes a 'headers' option
* which lets you set any headers you like
*/
var theUser = JSON.parse(localStorage.getItem("happuser"));
if (theUser !== null)
{
var new_options = _.extend({
beforeSend: function(xhr) {
var token = 'Bearer' + theUser.authtoken;
console.log('token', token);
if (token) xhr.setRequestHeader('Authorization', token);
}
}, options)
}
/*
* Call the stored original Backbone.sync method with
* extra headers argument added
*/
backboneSync(method, model, new_options);
};
Once I include this code, the API sends the request with a method of OPTIONS instead of GET and I obviously get a 405 invalid method response.
Here is the console log output
OPTIONS http://localhost:999/api/tasks 405 (Method Not Allowed) jquery-1.7.2.min.js:4
OPTIONS http://localhost:999/api/tasks Invalid HTTP status code 405
Any idea why the send method would be changing?
ADDITIONAL DISCOVERY:
It appears when I do a model.save it does the same thing., even if I don't actually change the model.
FURTHER DETAILS: This is the Request/Response for the call without authorisation...
Request URL:http://localhost:999/api/tasks
Request Method:GET
Status Code:200 OK
Request Headersview source
Accept:application/json, text/javascript, */*; q=0.01
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-GB,en-US;q=0.8,en;q=0.6
Host:localhost:999
Origin:http://localhost
Proxy-Connection:keep-alive
Referer:http://localhost/
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
Response Headersview source
Access-Control-Allow-Headers:*
Access-Control-Allow-Methods:GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin:http://localhost
Cache-Control:no-cache
Content-Length:3265
Content-Type:application/json; charset=utf-8
Date:Wed, 13 Nov 2013 14:51:32 GMT
Expires:-1
Pragma:no-cache
Server:Microsoft-IIS/7.5
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET
As soon as I add the sync override code in the response changes to this:
Request URL:http://localhost:999/api/tasks
Request Method:OPTIONS
Status Code:405 Method Not Allowed
Request Headersview source
Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-GB,en-US;q=0.8,en;q=0.6
Access-Control-Request-Headers:accept, authorization
Access-Control-Request-Method:GET
Host:localhost:999
Origin:http://localhost
Proxy-Connection:keep-alive
Referer:http://localhost/
User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
Response Headersview source
Access-Control-Allow-Headers:*
Access-Control-Allow-Methods:GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin:http://localhost
Allow:GET,POST
Cache-Control:no-cache
Content-Length:76
Content-Type:application/json; charset=utf-8
Date:Wed, 13 Nov 2013 14:56:52 GMT
Expires:-1
Pragma:no-cache
Server:Microsoft-IIS/7.5
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET
It would appear you are issuing a "not so simple request ™":
you're making a CORS request
and you're setting a custom header
In that case, your browser divides your request in two : a preflight request (the OPTIONS verb you see) and the actual request once the permissions have been retrieved.
To quote the article linked:
The preflight request is made as an HTTP OPTIONS request (so be sure
your server is able to respond to this method). It also contains a few
additional headers:
Access-Control-Request-Method - The HTTP method of the actual request.
This request header is always included, even if the HTTP method is a
simple HTTP method as defined earlier (GET, POST, HEAD).
Access-Control-Request-Headers - A comma-delimited list of non-simple
headers that are included in the request.
The preflight request is a way of asking permissions for the actual
request, before making the actual request. The server should inspect
the two headers above to verify that both the HTTP method and the
requested headers are valid and accepted.
I have a Rails service returning data for my AngularJS frontend application. The service is configured to allow CORS requests by returning the adequate headers.
When I make a GET request to receive data, the CORS headers are sent, as well as the session cookie that I have previously received on login, you can see for yourself:
Request URL:http://10.211.194.121:3000/valoradores
Request Method:GET
Status Code:200 OK
Request Headers
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Cache-Control:no-cache
Connection:keep-alive
Cookie:_gestisol_session=BAh7B0kiDHVzZXJfaWQGOgZFRmkASSIPc2Vzc2lvbl9pZAY7AEZJIiVmYTg3YTIxMjcxZWMxNjZiMjBmYWZiODM1ODQzMjZkYQY7AFQ%3D--df348feea08d39cbc9c817e49770e17e8f10b375
Host:10.211.194.121:3000
Origin:http://10.211.194.121:8999
Pragma:no-cache
Referer:http://10.211.194.121:8999/
User-Agent:Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
X-Requested-With:XMLHttpRequest
Response Headers
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:X-Requested-With,X-Prototype-Version,Content-Type,Cache-Control,Pragma,Origin
Access-Control-Allow-Methods:GET,POST,OPTIONS
Access-Control-Allow-Origin:http://10.211.194.121:8999
Access-Control-Max-Age:1728000
Cache-Control:max-age=0, private, must-revalidate
Connection:Keep-Alive
Content-Length:5389
Content-Type:application/json; charset=utf-8
Date:Mon, 04 Nov 2013 14:30:51 GMT
Etag:"2470d69bf6db243fbb337a5fb3543bb8"
Server:WEBrick/1.3.1 (Ruby/1.9.3/2011-10-30)
X-Request-Id:15027b3d323ad0adef7e06103e5aa3a7
X-Runtime:0.017379
X-Ua-Compatible:IE=Edge
Everything is right and I get my data back.
But when I make a POST request, neither the CORS headers nor the session cookie are sent along the request, and the POST is cancelled at the server as it has no session identifier. These are the headers of the request:
Request URL:http://10.211.194.121:3000/valoraciones
Request Headers
Accept:application/json, text/plain, */*
Cache-Control:no-cache
Content-Type:application/json;charset=UTF-8
Origin:http://10.211.194.121:8999
Pragma:no-cache
Referer:http://10.211.194.121:8999/
User-Agent:Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
X-Requested-With:XMLHttpRequest
Request Payload
{valoracione:{revisiones_id:1, valoradores_id:1}}
valoracione: {revisiones_id:1, valoradores_id:1}
And the service answers with a 403 because the request does not contain the session cookie.
I don't know why the POST request fails, as the $resource is configured just like the other one and I have defined the default for $httpProvider to send the credentials (and it works right as the GET request succeeds):
.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
}])
This is the failing resource when I call $save() on an instance:
'use strict';
angular.module('gestisolApp')
.service('ValoracionesService', ['$resource', 'API_BASE_URL', function ValoracionesService($resource, API_BASE_URL) {
this.valoraciones = $resource(API_BASE_URL + '/valoraciones');
}]);
And this is the service that succeeds with the query() call:
'use strict';
angular.module('gestisolApp')
.service('ValoradoresService', ['$resource', 'API_BASE_URL', function ValoradoresService($resource, API_BASE_URL) {
this.valoradores = $resource(API_BASE_URL + '/valoradores');
}]);
They are much like the same.
Does anybody know why the POST is sent without the session cookie?
Edit
Just to complete the information, preflight is handled by the following method, and is handled OK as the request before the failing POST is an OPTIONS that succeeds with a 200 response code:
def cors_preflight_check
headers['Access-Control-Allow-Origin'] = 'http://10.211.194.121:8999'
headers['Access-Control-Allow-Methods'] = 'GET,POST,OPTIONS'
headers['Access-Control-Allow-Headers'] = 'X-Requested-With,X-Prototype-Version,Content-Type,Cache-Control,Pragma,Origin'
headers['Access-Control-Allow-Credentials'] = 'true'
headers['Access-Control-Max-Age'] = '1728000'
render :nothing => true, :status => 200, :content_type => 'text/html'
end
This is the CORS OPTIONS request/response exchange previous to the failing POST:
Request URL:http://10.211.194.121:3000/valoraciones
Request Method:OPTIONS
Status Code:200 OK
Request Headers
Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:accept, x-requested-with, content-type
Access-Control-Request-Method:POST
Connection:keep-alive
Host:10.211.194.121:3000
Origin:http://10.211.194.121:8999
Referer:http://10.211.194.121:8999/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
Response Headers
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:X-Requested-With,X-Prototype-Version,Content-Type,Cache-Control,Pragma,Origin
Access-Control-Allow-Methods:GET,POST,OPTIONS
Access-Control-Allow-Origin:http://10.211.194.121:8999
Access-Control-Max-Age:1728000
Cache-Control:max-age=0, private, must-revalidate
Connection:Keep-Alive
Content-Length:1
Content-Type:text/html; charset=utf-8
Date:Mon, 04 Nov 2013 15:57:38 GMT
Etag:"7215ee9c7d9dc229d2921a40e899ec5f"
Server:WEBrick/1.3.1 (Ruby/1.9.3/2011-10-30)
X-Request-Id:6aa5bb4359d54ab5bfd169e530720fa9
X-Runtime:0.003851
X-Ua-Compatible:IE=Edge
Edit 2: I have changed the title to reflect clearly my problem
I had a similar problem and adding the following before angular $http CORS request solved the problem.
$http.defaults.withCredentials = true;
Refer https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS#Requests_with_credentials for more details.
When CORS is involved, then your browser will send an OPTIONS request before the POST request.
I don't know the specifics with Rails, but I guess you have to configure Rails to actually answer the OPTIONS request with the adequate CORS headers.
The following code is just for comparison - it shows how you would address the issue in Java:
public void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Access-Control-Allow-Origin", "http://10.211.194.121:8999");
resp.setHeader("Access-Control-Allow-Credentials", "true");
resp.setHeader("Access-Control-Allow-Methods", "OPTIONS, POST, GET");
resp.setHeader("Access-Control-Allow-Headers", "X-Requested-With,X-Prototype-Version,Content-Type,Cache-Control,Pragma,Origin");
resp.setHeader("Access-Control-Max-Age", "600");
resp.setHeader("Access-Control-Expose-Headers","Access-Control-Allow-Origin");
super.doOptions(req, resp);
}
But it might get you on the right track how to configure it in Rails.
Ok, finally I figured out what was happening.
By the answer posted on this question, I removed the HttpOnly parameter from the cookie and got it working on Firefox. Later for Chrome was just a matter of applying the rest of recommendations from the answer to make it work, like setting a domain for the cookie.