Angular 2 CORS issues - node.js

I have the following Angular 2 http request;
const endpoint = '<auth_url>';
const body = {
prompt: 'consent',
grant_type: 'authorization_code',
redirect_uri: '<redirect_url>',
code: '<authorization_code>'
};
const headers = new Headers();
headers.append('Authorization', 'Basic <auth>');
headers.append('Content-Type', 'application/x-www-form-urlencoded')
const options = new RequestOptions({ headers });
this.http.post(endpoint, body, { headers: headers })
.map(res => res.json())
.subscribe(
data => console.log(JSON.stringify(data)),
error => console.error(JSON.stringify(error)),
() => console.log('Request complete')
);
This is connecting to a node-oidc-provider which has been attached to an ExpressJS instance, the issue I am having is with CORS as the request ends up as OPTIONS on the server because of preflight. This shouldn't be the case as I have specified the Content-Type header as seen above. Annoyingly I am trying to figure out whether this is an issue with the server or my Angular2 code?
Would I need to explicitly enable CORS on an ExpressJS application and if not, why would setting the correct header on the POST have no effect?

Yes, you need to enable CORS on the server side. That is if you have control on the server.
Here are the Headers that should be returned from the server to enable CORS
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,PUT,DELETE
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
You can share some details about your server like if you want to add these headers from the code or in the WebServer itself.
Regarding the question about the reason why setting the headers in the POST request has no effect. The browser issues an OPTION request before your XHTTP request to ask the permission from the server about accepting CORS.
So, if you have control on the server, then you can add the headers I mentioned before. If not, then you can use some browser plugins to overcome this check.
Here is how the network tab in the developer tools should look like

Related

CORS blocking axios request with 'Authorization' Header and Data

Trying to send an axios post request from a Vue app (localhost) to my nodejs API (both localhost and heroku).
There are no issues receiving the response if the request is sent without data or headers, but as soon as I add them I get the following error:
Access to XMLHttpRequest at 'https://myapp.herokuapp.com/myendpoint' from origin 'http://localhost:8080'
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 have tried different options, both server and client side, as suggested on similar questions but had no success.
Client Request:
const apiUrl = 'https://myapp.herokuapp.com/myendpoint'
//const apiUrl = 'http://localhost:5000/myendpoint'
const token = Buffer.from(`${this.userid}:${this.password}`).toString('base64')
const data = {
'mydata': 'some data'
}
axios.post(apiUrl, data, {
headers: {
Authorization: "Basic " + token
}
}).then( res => {
console.log(res)
}).catch( err => {
console.log(err)
})
Server Endpoint:
app.post("/myendpoint", (req, res) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.send('This is the API\'s response')
})
Some of the answers I tried:
Response to preflight request doesn't pass access control check
Nodejs Express CORS issue with 'Access-Control-Allow-Origin'
https://www.moesif.com/blog/technical/cors/Authoritative-Guide-to-CORS-Cross-Origin-Resource-Sharing-for-REST-APIs/
CORS authorization issue while using axios
How to send authorization header with axios
I think it is better if you define your cors using a global middleware. First off, install cors by using npm i cors.
Then, I'll show an example of how that package could be used.
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
// your routes and things here...
Then, ensure that your front-end also uses withCredentials set to true in the axios request. This is done to ensure that the header is being sent properly.
axios.post(apiUrl, data, {
headers: {
Authorization: "Basic " + token
},
withCredentials: true,
}).then(() => ...);
Sometimes, if you define Access-Control-* manually, you might forget something. That's why I recommend you to use cors.

angular - node.js , HttpClient put connection succeeded but did not work successfully

I tried to use angular HttpClient get, delete, post to connect to the node.js API, all are successful.
But when I connected with the put method, I found that I can connect, but there is no other reflection.
For example:
Angular put to connect /api/edit/:id
import { HttpClient } from "#angular/common/http";
constructor( private http: HttpClient,) { }
editData(data: any, id: any){
this.http.put<any>(this.Url + "/api/edit/" + id, data).subscribe(res =>{
});
}
Node.js put API /api/edit/:id:
router.put('/api/edit/:id', (req, res, next) => {
console.log("edit api connected!");
res.status(200).json();
});
The working node.js server cmd window show like this log:
OPTIONS /api/edit/5c481dedbe234c2dec23c6a0 200 1.333 ms - 3
But not show "edit api connected!";
I don't know why the OPTIONS is displayed.
This can happen with other methods, like DELETE, but does not affect the operation.
I try to using postman to connect same AIP, It's work, the log like this:
edit api connected!
PUT /api/edit/testid 200 0.635 ms - -
I have tried to change router : /api/edit/ (without parameters), just can't work.
When you send requests via chrome or other browsers (using angular httpclient) to a different domain (not the same origin) there will be two requests. The first is OPTION a pre flight request to check which methods (GET, PUT, POST etc.) are allowed on server side. An OPTION request expects a response with CORS header:
For Expample:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: accept, authorization, content-type, x-requested-with, user-id
Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 1
Allow: HEAD, GET, OPTIONS
When this request fails the browser does not send the actual request PUT in your case. With Postman there is no OPTIONS pre flight, that is why it works.
To get your node.js server work with angular you have to use cors middleware e.g:
var express = require('express')
var cors = require('cors')
var app = express()
app.use(cors())
More information here: https://expressjs.com/en/resources/middleware/cors.html

express (using multer) Error: Multipart: Boundary not found, request sent by POSTMAN

Notice: only when I use form-data body form in Postman (which is the form I have to use because I want to send files beside text fields), I get:
Error: Multipart: Boundary not found.
when I use x-www-form-urlencoded everything is ok. (ofcourse when body-parser is used as middleware)
This is Request Content: (made by Postman)
POST /test HTTP/1.1
Host: localhost:3000
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Cache-Control: no-cache
Postman-Token: 61089a44-0b87-0530-ca03-a24374786bd1
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="test"
a simple word
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="data"
good
------WebKitFormBoundary7MA4YWxkTrZu0gW--
index.js:
var express = require('express');
var app = express();
var multer = require('multer');
var upload = multer();
app.post('/test', upload.array(), function (req, res, next) {
console.log(req.body.test);
console.log(req.body);
});
app.listen(3000, function () {
console.log('app started');
});
I found the solution. I only had to prevent Postman to send a Content-Type header. So I just removed it from request headers.
To give some insight on why that is happening,
When using content type multipart/form-data in any HTTP request, you can add a boundary information alongside the Content-Type specification like:
Content-Type: multipart/form-data; boundary=MyBoundary
You can replace MyBoundary with any string of your liking.
Then you will have to encode your form data (name=Abebe&age=5) as:
--MyBoundary
Content-Disposition: form-data; name="name"
Abebe
--MyBoundary
Content-Disposition: form-data; name="age"
5
--MyBoundary--
For more info read this StackOverflow question and answer
For JMeter and postman remove Content-Type from header.
it will resolve your issue.
I am going to expand a little bit on user9150719 for those who are having the same issue with the frontend side of things and are wondering where to remove the headers.
I had the same issue; I was trying to post from an Angular app to my Nodejs server. my post request included raw data and a file input. So I was thinking FormData()
Angular Service
//Declare header variables.
formDataHeader = {
headers: new HttpHeaders({
Accept: 'application/json',
'Access-Control-Allow-Origin': '*',
'Content-Type': 'multipart/form-data',
Authorization: 'Bearer ' + this._myService.getToken()
})
};
//Post function to Nodejs server
addNewContact(contact: FormData): any {
return this._httpClient.post(
environment.apiBaseUrl + '/contacts', // POST /api/contacts
(contact), // contact data,
this.formDataHeader
);
}
My formData was setup properly. I was able to get all the data, but the problem is that I had setup couple headers in my request that resulted in what user9150719 was experiencing.
My solution was to simplify my headers to this:
formDataHeader = {
headers: new HttpHeaders({
Authorization: 'Bearer ' + this._myService.getToken()
})
};
Another important thing to point out is that I didn't need to set the enctype="multipart/form-data" on my <form></form> tag.
Even though I had an httpInterceptor setup (I don't think it is working properly), I still needed to add the Authorization header on all my requests, but all other headers were resulting in my api call to return unexpected results.
Finally I think (but I am not entirely sure) that the reason why I didn't need to setup extra headers, is because in my NodeJS server, I already configured what headers to expect.
Node.JS Server
// app.js
app.use('/public/uploads', express.static('uploads'));
app.use('/public', express.static('public'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(cors());
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'x-www-form-urlencoded, Origin, X-Requested-With, Content-Type, Accept, Authorization, *');
if (req.method === 'OPTIONS'){
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, PATCH, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Credentials', true);
return res.status(200).json({});
}
next();
});
app.use(bodyParser.urlencoded({limit: '50mb', extended: true}));
app.use(bodyParser.json({limit: '50mb', extended: true}));
So I think that if your server is setup to handle certain types of headers (Content-Type, Authorization, Origin, etc.), You don't necessarily need to set those headers again on your frontend when you send your request to the server. There are certain exceptions, such Authorization which in certain cases need to be set; probably because they carry some data in the form of token or something in that regards.
I hope this helps someone out there!
If you are using fetch method
fetch("http://localhost:4000/upload_files", {
method: 'POST',
body: formData,
headers: {
"Content-Type": "multipart/form-data"
}
})
headers so that Fetch api automatically set the headers. Now remove headers or "Content-Type": "multipart/form-data"
fetch("http://localhost:4000/upload_files", {
method: 'POST',
body: formData
})
Now it works
Don't mention CONTENT-TYPE header while uploading files from FE using axios/fetch or anything 3rd HTTP handler.
Reason bcoz we don't know the boundary of our files. If you pass only 'multipart/form-data' as Content-Type, you will get an error since we aren't passing boundary in there.
So, let the browser add it (multipart/form-data) along with Boundary based on the files attached.
AND if you want to send some data along with files, you should be sending them as a multipart/form-data(Again we don't need to add this header manually) type only.
We CANNOT send multiple Content-Type data at once for any http call.
Please refer below code.
async onUpload(){
const formData=new FormData();
formData.append('files', file1);
formData.append('files', file2);
formData.append('data', {key1:value1, key2:value2});
const res=await axios.post(URL, formData);
}
FYI #hassam-saeed
Just if someone has the same issue i had.
NestJs - BackEnd
NextJs - FrontEnd
I was trying to do something like:
const formData = new FormData()
formData.append('context', body.context.toString())
body.files.map((file) => {
formData.append('files', file, `${file.name}`)
})
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data',
...(access_token ? { Authorization: `Bearer ${access_token}` } : {}),
},
body: formData,
})
But because this 'Content-Type' overrides the browsers setting of 'Content-Type' AND the content-length is not explicitly set (which was the real issue i think) ,the form-data was showing up on the backend still encoded. So NestJS was not able to parse the 'context' variable or the 'files'.
Also, if you use api routes in NextJS, remeber to check that no-where in there is the 'Content-Type' overridden.... I also had this issue.
I did this instead
const form = new FormData();
headers['Content-Type'] = `multipart/form-data; boundary=${form._boundary}`;
Source: https://stackoverflow.com/a/54796556/8590519

Cannot set headers for Express.js app (CORS)

No matter what I try to use to set the headers it simply won't work.
The server side needs to send the response headers like this, after accepting the POST request from Frontend.
server.js
app.post('/register', (req, res) => {
res.set({
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
});
res.send('string');
res.end();
}
The res.set() function was not the only function I tried to use.
I tried with:
res.writeHead(201, {'Access-Control-Allow-Origin': '*'}) - doesn't work
res.setHeader('Access-Control-Allow-Origin', '*') - nope
res.headers() - also doesn't work
It says here that it goes like this: How can I set response header on express.js assets but the answer doesn't work for me.
No matter what I tried, the server just won't respond with the CORS enabled header and I need that header property. What am I doing wrong?
Try using the cors module: https://www.npmjs.com/package/cors
var cors = require('cors')
...
app.use(cors())
The Access-Control-Expose-Headers response header indicates which headers can be exposed as part of the response by listing their names.
You can read more about it here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
res.set('Access-Control-Expose-Headers', 'Access-Control-Allow-Origin, <any-other-custom-header>, ...');

Cross-domain POST request in Node.JS with preflight?

I have just started with Node.
I am trying to get cross-domain form data from an HTML form to parse in a Node.js server. I have been able to do this with simple POST data, not with POST requests that require preflight.
I am running the Node code on cloud9 app servers. I am also using the Cors module to handle the requests. This module works well with simple requests (test here to see a simple request work), however with requests that require preflight I get this result from the Chrome inspector console.
XMLHttpRequest cannot load https://nms-motaheri-1.c9.io:8080/mail.
The request was redirected to 'https://c9.io:8080/api/nc/auth?.....SHORTENED',
which is disallowed for cross-origin requests that require preflight.
Here is my server.js code:
// Define dependencies
var express = require('express')
, cors = require('cors')
, app = express()
, parse_post = require("parse-post");
// Core module config
var corsOptions = {
origin: '*',
preflightContinue: true // <- I am assuming this is correct
};
app.use(cors(corsOptions));
// Respond to option request with HTTP 200
// ?? Why is this not answering my OPTION requests sufficiently ??
app.options('*',function(req,res){
res.send(200);
});
// Give a hello world response to all GET requests
app.get('/',function(req,res){
res.sendFile(__dirname + '/index.html');
});
// Handle all POST requests to /mail
app.post('/mail', parse_post(function(req, res) {
console.log(req.body);
res.json({msg: 'This is CORS-enabled for all origins!'});
})
);
// Listen on default Cloud9 port which is 8080 in this case
app.listen(process.env.PORT, function(){
console.log('CORS-enabled web server listening on port ' + process.env.PORT);
});
Why is this happening and how can I satisfactorily answer the OPTION request for my POST with pre-flight?
Here is the post request and response in Chrome dev tools:
Turns out that part of the problem was that the cloud9 server was set to private making these requests all redirect.
After making the server public, the redirections stopped. However, I received an error that the Node.js server did not have any Access-Control-Allow-Origin headers to allow requests from my cross origin domain. I noticed that "simple" with-out preflight requests would go through. So instead of trying to understand why it was not accepting my allow-all-origin-configuration on the Node.js side I decided to serialized the POST data to get rid of the preflight requirement and changed the data type in my angular request to plain text.
To get rid of preflight, first get rid of any POST header configuration (cache, etc), make sure your request Content-Type is plain text and make sure your actual content is plain text too. So if it is in JSON serialize it in jQuery before sending it with POST.
This is what my new Angular Post request code looked like:
sendEmail: function(email) {
var config = {
headers: {
'Content-Type': 'text/plain'
}
};
var POSTDATA= JSON.stringify(POSTDATAJSON);
return $http.post(POSTURL, POSTDATA, config)
}
And in Node.js this, I am using the cors Node.js module:
app.post('/mail', parse_post(function(req, res) {
var postReq = JSON.parse(Object.keys(req.body));
}));

Resources