Cookie accessible with GET requests but not with PUT or POST requests - express

I'm using React for the front-end, an Express API for our back-end, and I'm experiencing an issue with accessing our cookie with Axios' PUT or POST requests.
On Postman, i need to declare the cookie i need to make my front-end authorized on connecting to my API.
I'm doing it like this on Postman's interface :
Example of passing cookie in Postman
And when i'm calling my API's requests, on Postman, with the cookie set it works and looks like this in the Postman's code snippet :
var data = JSON.stringify({
"attributes": {
"test": test
}
});
var config = {
method: 'put',
url: 'APIurl/test',
headers: {
'Authorization': 'Bearer myPersonnalToken',
'Content-Type': 'application/json',
'Cookie': 'key=theCookieINeedToGetInMyAxiosRequest'
},
data: data
};
axios(config)
.then(function (response) {
console.log(JSON.stringify(response.data));
})
.catch(function (error) {
console.log(error);
});
Ok so here it is working on Postman, time to make it real on my app with Axios, like this in my front-end :
var url = 'APIurl/test'
var data = {
test: test
}
var config = {
headers: {
'Authorization': "Bearer " + myPersonnalToken,
'Content-Type': 'application/json',
},
withCredentials: true,
}
axios.put(url, JSON.stringify(data), config)
.then(res => {
console.log(res)
...
})
.catch(err => {
console.log(err)
...
})
And knowing it's cross-domain, like this in my back-end :
const corsOptions = {
// I tried with * to see if I mispelled the origin, but no
origin: theOriginIWant,
credentials: true
}
app.use(cors(corsOptions))
As I read a hundred times, {withCredentials: true} should be enough to pass my cookie in my request, and my CORS options look great to me.
But when I launch my app and click the button which calls the request, this is what my devtools Network looks like :
General
Request URL: APIurl/test
Referrer Policy: strict-origin-when-cross-origin
Request Headers
Accept: application/json
Authorization: Bearer myPersonnelToken
Content-Type: application/json
Referer: myAppUrl
sec-ch-ua: "Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36
Request Payload
test=test
As you can see, my cookie is missing...
A 401 error is thrown because I cannot access the API without the cookie, with the informations below :
Access to XMLHttpRequest at 'APIurl/test' from origin 'myAppUrl' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I tried everything i saw on the web, but nothing worked for me, i just can't pass the cookie i want to my PUT or POST requests, which throws an error.
I really don't understand why GET requests work, but not POST or PUT ones...
Any idea of how to fix this ?
Thanks in advance,
Hugo GB

Related

Express Node.js Cors preflight issue 400 net::ERR_FAILED

I have been banging my head against the wall for hours now... tried every type of combination to get this working.
I have my own API endpoint here: https://app.automate.mn/api/tools/email/add
Which I am trying to post data to from 3rd party websites.
app.js
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
app.options('*', cors());
...
const {addContact} = require('./routes/api/tools/email');
app.post('/api/tools/email/add', addContact);
addContact.js
addContact: async function (req, res) {
res.status(200).send('OK - TEST');
return;
}
I have setup a test website here with the form: https://demoautomatemn.wpcomstaging.com/
When you submit the form, it's posting the data to the endpoint:
loadForm.js
automate_post_data('https://app.automate.mn/api/tools/email/add', data)
.then(data => {
console.log(data); // JSON data parsed by `data.json()` call
});
async function automate_post_data(url = '', data = {}) {
const response = await fetch(url, {
method: 'POST',
mode: 'cors', // no-cors, *cors, same-origin
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
//referrerPolicy: 'origin', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: JSON.stringify(data) // body data type must match "Content-Type" header
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status} - ${response.statusText}`);
}
return response.json();
}
The post is failing:
Access to fetch at 'https://app.automate.mn/api/tools/email/add' from origin 'https://demoautomatemn.wpcomstaging.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
loadForm.js:109 POST https://app.automate.mn/api/tools/email/add net::ERR_FAILED
I was expecting to see the "Access-Control-Allow-Origin" in the response headers for the OPTION call but nothing (Preflight response is not successful). I have tried everything to get it working.
Any suggestions would be so welcome right now.
OK, Progress but no cigar!
I setup the following:
Setting at apache level:
https://docs.bitnami.com/bch/infrastructure/nodejs/administration/enable-cors-nodejs/
You might also find this helpful: https://enable-cors.org/server_apache.html
Adding origin to the request
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Origin': window.location.href
},
referrerPolicy: 'origin',
This then resulted in the error message changing:
Access to fetch at 'https://app.automate.mn/api/tools/email/add' from origin 'https://demoautomatemn.wpcomstaging.com' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
Update 'Content-Type': 'application/x-www-form-urlencoded',
As per this post: Why doesn't adding CORS headers to an OPTIONS route allow browsers to access my API?
I updated the request to x-www-form-urlencoded
const response = await fetch(url, {
method: method,
mode: 'cors', // no-cors, *cors, same-origin
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': window.location.href
},
referrerPolicy: 'origin', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
body: data // body data type must match "Content-Type" header
});
It feels like progress however I am still getting 406 error:
POST https://app.automate.mn/api/tools/email/add 406
Response {type: "cors", url: "https://app.automate.mn/api/tools/email/add", redirected: false, status: 406, ok: false, …}

AXIOS POST failed access control check: No 'Access-Control-Allow-Origin' .HTTP status code 405

I don't know what I did wrong and need help.
function loadDoy() {
axios({
method: 'post',
headers: {
"Access-Control-Allow-Origin": "*",
'Content-Type': 'application/json',
},
url: apiPostUrl,
data: {
user: "webuser",
password: "m0nk3yb#rz",
layout: "Main Menu"
},
})
.then(function(response) {
this.token = response.data.token;
//if login token works then get records
getRecords();
})
.catch(function(xhr, ajaxOptions, thrownError) {
alert(xhr.status);
alert(thrownError);
});
}
You can use 'crossDomain': true at your header of axios post, this is due to cors error. Your browser is making a Preflight Request, which uses the OPTIONS HTTP method. This is to check whether the server will allow the POST request – the 405 status code is sent in the response to the OPTIONS request, not your POST request
In this case, the following is causing the request to be preflighted:
if the Content-Type header has a value other than the following:
application/x-www-form-urlencoded
multipart/form-data
text/plain
The value for the Content-Type header is set to application/json;charset=utf-8 by axios. Using text/plain;charset=utf-8 or text/plain fixes the problem: You may try using like below.
source: App Script sends 405 response when trying to send a POST request
axios({
method: 'post',
headers: {
'crossDomain': true,
//'Content-Type': 'application/json',
'Content-Type': 'text/plain;charset=utf-8',
},
url: apiPostUrl,
data: {
user: "webuser",
password: "m0nk3yb#rz",
layout: "Main Menu"
},
})
.then(function(response) {
this.token = response.data.token;
//if login token works then get records
getRecords();
})
.catch(function(xhr, ajaxOptions, thrownError) {
alert(xhr.status);
alert(thrownError);
});

Aurelia HttpClient.Fetch to get token from Auth0 but works fine in Postman

I have no trouble getting a bearer token returned when using Postman. However, when using Aurelia, I receive a status 200 with "OK" as the only response. I see that the Request Method is still "OPTIONS". I see this in the Chrome Console:
Failed to load https://------.auth0.com/oauth/token: Request header field Access-Control-Allow-Origin is not allowed by Access-Control-Allow-Headers in preflight response.
But, from what I can see the headers shown in the response and from what I'm seeing everything looks like it's there.
Here's what I receive from Postman:
Response: Status 200 OK
JSON:
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGci...{shortened for brevity}",
"expires_in": 86400,
"token_type": "Bearer"
}
Here's code from Aurelia:
private getToken() {
var postData = { "client_id": API_CONFIG.clientId, "client_secret": API_CONFIG.clientSecret, "audience": API_CONFIG.audience, "grant_type": "client_credentials" };
this.http.fetch('https://kimberlite.auth0.com/oauth/token', {
credentials: 'omit',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': 'http://localhost:3000/'
},
mode: 'cors',
method: 'post',
body: JSON.stringify(postData)
}).then(result => result.json())
.then(data => {
localStorage.setItem('api_access_token', data.access_token);
localStorage.setItem('api_expires_at', new Date().getTime() + data.expires_in);
});
}
I've searched and haven't found anything that's helped me get passed this. What am I missing? Any help greatly appreciated
After reading Jesse's comment below, I removed the header for the 'Access-Control-Allow-Origin' and receive the same 200 OK. However, receive error in Google Chrome Origin 'localhost:3000'; is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.".
After reading other questions, I attempted removing all headers and I receive a 401 Unathorized with the following response {{"error":"access_denied","error_description":"Unauthorized"}
private getToken() {
var postData = { "client_id": API_CONFIG.clientId, "client_secret": API_CONFIG.clientSecret, "audience": API_CONFIG.audience, "grant_type": "client_credentials" };
let http = new HttpClient();
http.fetch('https://kimberlite.auth0.com/oauth/token', {
credentials: 'omit',
//headers: {
// 'Content-Type': 'application/json'
//},
mode: 'cors',
method: 'post',
body: JSON.stringify(postData)
}).then(result => result.json())
.then(data => {
localStorage.setItem('api_access_token', data.access_token);
localStorage.setItem('api_expires_at', new Date().getTime() + data.expires_in);
});
}
ok, I just tried in Firefox, using only the 'Content-Type' header and received expected response. Is there something with Chrome (which most users are going to be using) that I need to be aware of?
You shouldn't set the access-control-allow-origin header on the request. In a CORS request, the server endpoint needs to set this header on the response of your OPTIONS request.
The way that Cross-Origin Resource Sharing works, is that the client first makes an OPTIONS call to the server endpoint. The server endpoint should be configured to use CORS, and have a list of origins that are allowed (or simply a * to allow all origins). Then on the response to this OPTIONS request, the server will set the Access-Control-Allow-Origin: https://localhost:3000 to indicate the origin is allowed to make the request. You can see this in your response too:
The client then proceeds to make the GET or POST call to the same endpoint and actually retrieve/store the data.
In your case, if you make the request using the Aurelia fetch client, you don't need to set a header to do this. You can simply do the following:
private getToken() {
var postData = { "client_id": API_CONFIG.clientId, "client_secret": API_CONFIG.clientSecret, "audience": API_CONFIG.audience, "grant_type": "client_credentials" };
this.http.fetch('https://kimberlite.auth0.com/oauth/token', {
credentials: 'omit',
headers: {
'Content-Type': 'application/json'
},
mode: 'cors',
method: 'post',
body: JSON.stringify(postData)
}).then(result => result.json())
.then(data => {
localStorage.setItem('api_access_token', data.access_token);
localStorage.setItem('api_expires_at', new Date().getTime() + data.expires_in);
});
}

Angular 2 Token: Response for preflight has invalid HTTP status code 400

I have an Angular2/TypeScript application running i Visual Studio Code.
An API running in VS 2015. This is the API project: http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api
I can use the API and create new users, but when I try to login(Use the Token function), then I get the following error:
XMLHttpRequest cannot load https://localhost:44305/Token. Response for preflight has invalid HTTP status code 400
The header looks like this:
Request URL:https://localhost:44305/Token
Request Method:OPTIONS
Status Code:400
Remote Address:[::1]:44305
Response Headers
cache-control:no-cache
content-length:34
content-type:application/json;charset=UTF-8
date:Wed, 10 Aug 2016 19:12:57 GMT
expires:-1
pragma:no-cache
server:Microsoft-IIS/10.0
status:400
x-powered-by:ASP.NET
x-sourcefiles:=?UTF-8?B?QzpcQ2hlY2tvdXRcQVBJXzJ2czJcQVBJXEFQSVxUb2tlbg==?=
Request Headers
:authority:localhost:44305
:method:OPTIONS
:path:/Token
:scheme:https
accept:*/*
accept-encoding:gzip, deflate, sdch, br
accept-language:en-US,en;q=0.8,da;q=0.6,nb;q=0.4
access-control-request-headers:authorization
access-control-request-method:POST
cache-control:no-cache
origin:http://evil.com/
pragma:no-cache
referer:http://localhost:3000/signin
user-agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36
My angular service looks like this:
loginAccount(account: Account): Observable<string> {
var obj = { Email: account.Email, Password: account.Password, grant_type: 'password' };
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions( {method: RequestMethod.Post, headers: headers });
let body = JSON.stringify(obj);
console.log('loginAccount with:' + body);
return this._http.post('https://localhost:44305/Token', body, options)
.map(this.extractData)
.catch(this.handleError);
}
When I use the AJAX funtions that a in the API project: http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api then it works fine ?? What am I doing wrong in the Angular POST request ?
I found the solution. Thanks to the comments on the API site: http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api
I had to set the correct header for application/x-www-form-urlencoded; charset=UTF-8 and serialize the object i posted. I can´t find an Angular serializer method, so I made my own(copy from another stackoverflow site) in JavaScript.
Here is the final call when the user login on the API and request a token, when using Angular2 & TypeScript:
loginAccount(account: Account): Observable<string> {
var obj = { UserName: account.Email, Password: account.Password, grant_type: 'password' };
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' });
let options = new RequestOptions( {method: RequestMethod.Post, headers: headers });
let body = this.serializeObj(obj);
return this._http.post('https://localhost:44305/Token', body, options)
.map(this.extractData)
.catch(this.handleError);
}
private serializeObj(obj) {
var result = [];
for (var property in obj)
result.push(encodeURIComponent(property) + "=" + encodeURIComponent(obj[property]));
return result.join("&");
}
I was also facing same issue from last week and searched on google and stack overflow but all solutions in vein. but after lot of reading and investigation we have found below solution, we were facing issue in only POST method,GET called successfully.
Instead of directly passing Options we need to first stringify option object like JSON.stringify(options)
CreateUser(user:IUser): Observable<void> {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Accept', 'application/json');
let options = new RequestOptions({ headers: headers });
return this._http.post('http://localhost:22736/api/Employee/Create', **JSON.stringify(options)**)
.map((res: Response) => {
return res.json();
})
.catch(this.handleError);
}
It worked for me, Hope it will help others too.
I found that in angular 4 you have to make it like this.
public addQuestion(data: any): Observable<Response> {
let headersObj = new Headers();
headersObj.set('Content-Type', 'application/x-www-form-urlencoded');
let requestArg: RequestOptionsArgs = { headers: headersObj, method: "POST" };
var params = new URLSearchParams();
for(let key of Object.keys(data)){
params.set(key,data[key]);
};
return this.http.post(BaseApi.endpoint + 'Question', params.toString(), requestArg)
.map((res: Response) => res.json().data);
}
Another native solution is by using HttpParams class and it's toString() method:
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' });
let options = { headers, observe: 'response' };
const body = new HttpParams()
.set('grant_type', 'password')
.set('username', accountInfo.username)
.set('password', accountInfo.password);
return this._http.post('https://localhost:44305/Token', body.toString(), options)
toString() - Serialize the body to an encoded string, where
key-value pairs (separated by =) are separated by &s.
Note. Also it works without setting the headers

React-dropzone Upload to S3 from client returns 400 bad request

I am trying to implement this code example here to upload an image to an S3 bucket.
My server seems to return the pre-signed URL OK, but the PUT request that follows fails with a 400 error.
Here is the server-side pre-sign code:
var s3 = new aws.S3();
var params = {
Bucket: secrets.aws.bucket,
Key: body.filename,
ContentType: body.filetype,
ACL: 'public-read'
};
s3.getSignedUrl('putObject', params, function(err, data) {
if (err) {
console.log(err);
return err;
} else {
console.log('URL sent back to client ------------------------------------');
res.status(200).send(data);
}
});
And here is the upload action client-side:
export function uploadFile(data) {
var file = data[0];
return dispatch => {
dispatch(dropFileAccepted(data));
return makeUploadRequest('post', {filename: file.name, filetype: file.type}, '/signURLforS3')
.then(function (result) {
var signedUrl = result.data;
var options = {
headers: {
'Content-Type': file.type,
'x-amz-acl': 'public-read',
'x-amz-region': 'eu-central-1'
}
};
return axios.put(signedUrl, file, options);
})
.then(function (result) {
console.log(result);
})
.catch(function (err) {
console.log(err);
});
};
}
From the network request/response headers it looks like the content-type and CORS configuration on the bucket are correctly set, but I'm unsure whether there could be an issue with needing to set the region.
or do I need some additional settings on the bucket, like a bucket policy, or specify a Signature version ?
Request URL:https://XXXXXXXXXX.s3.amazonaws.com/brand.png?AWSAccessKeyId=AKXXXXXXXXXXXX&Content-Type=image%2Fpng&Expires=1460128516&Signature=%2BooCHlrwelBYC9fMYnu01PokgWM%3D&x-amz-acl=public-read
Request Method:PUT
Status Code:400 Bad Request
Remote Address:54.231.192.36:443
Response Headers
Access-Control-Allow-Methods:PUT, POST, GET, HEAD
Access-Control-Allow-Origin:*
Access-Control-Max-Age:3000
Connection:close
Content-Type:application/xml
Date:Fri, 08 Apr 2016 15:00:17 GMT
Server:AmazonS3
Transfer-Encoding:chunked
Vary:Origin, Access-Control-Request-Headers, Access-Control-Request-Method
x-amz-id-2:rXMCu6YD5mLrN3beBCs+kmXDGzhzrQHV2fTUTNooWXBQuPfLNOKDcArGQWRj+NLk+zo=
x-amz-region:eu-central-1
x-amz-request-id:FC181ED154
Request Headers
Accept:application/json, text/plain, */*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4,fr-FR;q=0.2,fr;q=0.2
Connection:keep-alive
Content-Length:16819
Content-Type:image/png
Host:XXXXXXX.s3.amazonaws.com
Origin:http://localhost:3000
Referer:http://localhost:3000/admin/blog/create
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36
x-amz-acl:public-read
x-amz-region:eu-central-1
Query String Params
AWSAccessKeyId:AKXXXXXXXX
Content-Type:image/png
Expires:1460128516
Signature:+ooCHlrwelBYu01PokgWM=
x-amz-acl:public-read
Thanks in advance for any pointers. I've been pulling my hair out over this...