CORS preflight response doesn't match actual response - node.js

In my node.js server I have included CORS as middleware like so:
app.use(cors({ origin: 'http://<CORRECT_ORIGIN_URL>:3030', credentials: true }))
I'm using Apollo Client in the app that sends the request, and have set credentials to 'include' when initialising ApolloClient, like so:
// Create a WebSocket link
const wsLink = process.browser ? new WebSocketLink({
uri: `ws://<CORRECT_REQUEST_URL>:8000/graphql`,
options: {
reconnect: true,
},
}) : null
// Create an http link (use batch, allow cookies response from server)
const httpLink = new BatchHttpLink({
uri: 'http://<CORRECT_REQUEST_URL>/api/',
credentials: 'include'
})
// Split terminating link for websocket and http requests
const terminatingLink = process.browser ? split(
({ query }) => {
const { kind, operation } = getMainDefinition(query)
return kind === 'OperationDefinition' && operation === 'subscription'
},
wsLink,
httpLink,
) : httpLink
// Create Apollo client
const client = new ApolloClient({
link: ApolloLink.from([authLink, errorLink, terminatingLink])
})
When I attempt to sign-in, I can see that a preflight OPTIONS request is sent and gets the correct response back:
Request Headers (OPTIONS request)
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST
Origin: http://<CORRECT_ORIGIN_URL>:3030
Referer: http://<CORRECT_ORIGIN_URL>/login
Response Headers (OPTIONS request)
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Origin: http://<CORRECT_ORIGIN_URL>:3030
Connection: keep-alive
Content-Length: 0
Date: Wed, 20 Mar 2019 03:09:14 GMT
Server: nginx/1.15.5 (Ubuntu)
Vary: Origin, Access-Control-Request-Headers
X-Powered-By: Express
Yet when the actual POST request is sent, I get the following response:
Response Headers (POST request)
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/json
Date: Wed, 20 Mar 2019 03:09:15 GMT
Server: nginx/1.15.5 (Ubuntu)
Transfer-Encoding: chunked
Vary: Accept-Encoding, Origin
X-Powered-By: Express
I have no idea why the response headers are different in the post request when the options preflight show that it should be correct.
This incorrect POST response leads to the following error message on the client:
Access to fetch at 'http://<CORRECT_REQUEST_URL/api/' from origin
'http://<CORRECT_ORIGIN_URL>:3030' has been blocked by CORS policy:
The value of the 'Access-Control-Allow-Origin' header in the response
must not be the wildcard '*' when the request's credentials mode is
'include'.
I've tried googling and searching stackoverflow for a solution but can't find anything. Any ideas?

I solved my problem.
The issue is that Apollo Server adds CORS middleware by default, which was overriding my CORS settings. From Apollo's documentation:
Provide false to remove CORS middleware entirely, or true to use your
middleware's default configuration.
The default value is true.
To solve the issue, I simply had to disable CORS functionality in Apollo, which simply required setting cors: false in .applyMiddleware like so:
server.applyMiddleware({
app,
path: '/',
cors: false,
});
Further reference:
https://github.com/expressjs/cors/issues/134#issuecomment-413543241

I had similar issues with apache2 proxy running in front of my Express services. The proxy was caching some (only some!) of the responses. This is what I added to the apache config and it solved the problem:
Header set Cache-Control "no-cache, must-revalidate" env=no-cache-headers
Header set Pragma "no-cache" env=no-cache-headers
Header set Expires "Sat, 1 Jan 2000 00:00:00 GMT" env=no-cache-headers

Related

Unable to set cookie from node server -> app

I'm trying to set an httpOnly cookie from my node.js api (localhost:3001) to work with my react client app (localhost:3000), everything I've tried so far results in no cookie being set in my browser. Some key factors about my setup:
Backend is node, running fastify, fastify-cookie & cors
// CORS
server.use(
require('cors')({
origin: ['https://localhost:3000'],
optionsSuccessStatus: 200,
credentials: true
})
)
// Cookies
server.register(require('fastify-cookie'), {
secret: process.env.JWT_SECRET
})
// Sending the cookie
reply
.setCookie('token', token, {
domain: 'localhost',
path: '/',
secure: true,
sameSite: 'lax',
httpOnly: true
})
.send({ user })
Client is running https localhost in chrome, making api calls using fetch.
const fetchUsers = async () => {
const req = await fetch(`${process.env.USERS_API_BASE}/users`, { credentials: 'include' })
const res = await req.json()
console.log(res)
}
Result
No cookie is ever set in my chrome application inspector, but it is sent to the browser from the server and looks correct.
set-cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsImVtYWlsIjoiaGVsbG9Ac2hhbi5kaWdpdGFsIiwiaWF0IjoxNjIwNDI1ODI0LCJleHAiOjE2MjA0Mjk0MjR9.S8eOQMtSBY85wlenuxjIGYNuk3Ec5cKQ87pAhmCvQ9w.nfRxGzq3IMFimC%2FSJeUH9Xl7bH%2FyXVprwK1NBYfur4k; Domain=localhost; Path=/; HttpOnly; Secure; SameSite=Lax
request.cookies on the sever always returns a blank object {}. Any suggestions?
What you are facing is a CORS error OR at least it is categorized as one..
you see the server seems to think you're making a cross-domain request..
If you log the responce Headers this is typically what you would see
HTTP/1.1 200 OK
Date: Sun, 20 May 2018 20:43:05 GMT
Server: Apache
Set-Cookie: name=value; expires=Sun, 20-May-2018 21:43:05 GMT; Max-Age=3600; path=/; domain=.localHost
Cache-Control: no-cache, private
Access-Control-Allow-Origin: http://localHost:8080
Vary: Origin
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
Content-Length: 2
Keep-Alive: timeout=10, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
but when you are making a request you kinda send it like this
const res = await axios({ method: 'POST', url: 'http://127.0.0.1:3000/api/v1/users/login', data: { email, password } });
Do you see the problem 127.0.0.1 != http://localhost:8000 and that is the solution to your problem
In short Check the Key=value Pair of Access-Control-Allow-Origin on your response and Request the domain names should match else the cookie won't be set on the browser...
Here is a GitHub Issue Link for this same problem

Angular 8 Express Post Throws a CORs Error

Yes I know you can normally fix these by setting the origin wilcard to '*' but I'm using "credentials : true", so that's thrown my normal solution out the window.
The Issue I'm having is that when I try to perform a post request to Express, it seems to first submit as an OPTIONS, then get redirected as GET, then redirected again as POST.
All my "Access-Control-Allow-XXXX" headers appear in the original OPTIONS but then gets lost in the redirects.
The error being shown from the browser is;
Access to XMLHttpRequest at 'http://localhost:4200/' (redirected from 'http://localhost:3000/api/save') from origin 'null' has been blocked by CORS policy: Request header field access-control-allow-origin is not allowed by Access-Control-Allow-Headers in preflight response.
Here's my express cors setup, the dynamic origin was set because I thought the direct might have been looking for the API address as a possible origin, and then undefined and null because I got annoyed with it;
var app = express();
var port = process.env.PORT || 3000;
whitelist = [UI_BASE_URL, API_BASE_URL, undefined, null];
app.use(
cors({
allowedHeaders: [
'Origin',
'X-Requested-With',
'Content-Type',
'Accept',
'X-Access-Token',
'Access-Control-Allow-Origin',
'Access-Control-Allow-Headers',
'Access-Control-Allow-Methods'
],
preflightContinue: false,
credentials: true,
origin: function(origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
methods: 'GET,HEAD,OPTIONS,PUT,PATCH,POST,DELETE',
})
);
And here's the service call from angular
const httpOptions = {
headers: new HttpHeaders({
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT',
'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
}),
};
save(myData): Observable<any> {
return this.http.post(
this.workSpaceReportURL + 'save',
{
data: myData,
withCredentials: true,
},
httpOptions
);
}
and because they might be useful, the request and response headers from the POST.
RESPONSE
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://localhost:4200
Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate
Connection: keep-alive
Content-Length: 43
Content-Type: text/plain; charset=utf-8
Date: Mon, 11 Nov 2019 14:50:31 GMT
Expires: 0
Location: http://localhost:4200
Pragma: no-cache
Set-Cookie: connect.sid=s%3Aj3wYyE_I14o6b_C-L6EDnOxesW0CBVks.WEwBNiQegZ1ufG2d40%2BF4BGfbCWPbOz9FS4Kj%2BC14Pc; Path=/; Expires=Mon, 18 Nov 2019 13:30:31 GMT; HttpOnly
Strict-Transport-Security: max-age=15552000; includeSubDomains
Surrogate-Control: no-store
Vary: Origin, Accept
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 184
Content-Type: application/json
Host: localhost:3000
Origin: http://localhost:4200
Referer: http://localhost:4200/data-controller
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36
I've tried a number of different solutions I've come across on SO but they normally end up being fixed by the usual wildcard origin.
Thanks for any suggestions you can make.
It looks like you used an old way to add cord headers. Have you tried doing this?
var cors = require('cors')
app.use(cors())
Make sure to run the last one before you run your other app.use
Ok, I think I realised what the solution was. I'd just spent so long starting at it to realise.
I'd managed to strip out the Content-Type without noticing, and was passing the withCredentials in as part of the body, not the httpOptions (which I tihnk was my original problem).
In case it helps someone else out a few years from now, the httpOptions and post should've looked like the below.
I'm now going to go stand in the server room of shame.
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
}),
withCredentials: true,
};
save(myData): Observable<any> {
return this.http.post(
this.workSpaceReportURL + 'save',
{
myData,
},
httpOptions
);
}

Nodejs API : CORS seems to be enabled by default

Here is my problem: I have a site exemple.com and a node server where is my API running on exemple.com:3000. I want my API to be accessible only by exemple.com.
So I thought that Same-Origin Policy would have block every request from every site except mine if I send in the response the header : Access-Control-Allow-Origin: https:exemple.com.
The problem is that if I call the url : http://exemple.com:3000/api/v1/test/search on another site, I have the error:
Failed to load https://exemple:3000/api/v1/test/search: The 'Access-Control-Allow-Origin' header has a value 'https://exemple.com' that is not equal to the supplied origin. Origin 'http://randomsite.com' is therefore not allowed access.
But in the network tab, I have the response with a 200 status and all the results.
How is it possible to have the error and the result at the same time ? Here is my code :
app.js
const corsApiOptions = {
origin: 'https://exemple.com',
originSuccessStatus: 200,
};
app.use('/api/v1', cors(corsApiOptions), api);
routes/api.js
const express = require('express'); const mongoose =
require('mongoose'); const mongoosastic = require('mongoosastic');
const AssoSchema = require('../schemas/assoSchema');
const router = express.Router();
router.post('/assos/search', (req, res) => { let { query } =
req.body; if (query && query.length > 0) {
query = query
.split(' ')
.map(el => `${el}~`)
.join(' ');
mongoose.connect('mongodb://localhost/mydb');
AssoSchema.plugin(mongoosastic);
const Asso = mongoose.model('asso', AssoSchema, 'assos');
Asso.search(
{
query_string: { query },
},
{ hydrate: true },
(err, results) => {
if (err) res.json({ error: err });
res.json(results.hits.hits);
},
); } else res.json(''); });
module.exports = router;
Here are the results if I make an ajax request from the console of randomsite.com
//General
Request URL: https://exemple.com:3000/api/v1/test/search
Request Method: POST
Status Code: 200 OK
Remote Address: X.X.X.X:3000
Referrer Policy: no-referrer-when-downgrade
//Response Headers
Access-Control-Allow-Origin: https://exemple.com
Connection: keep-alive
Content-Length: 10022
Content-Type: application/json; charset=utf-8
Date: Fri, 30 Mar 2018 10:52:25 GMT
ETag: W/"2726-3/3tY5WvDTtyKm4KPBifg2dP7mI"
Vary: Origin
X-Powered-By: Express
//Request Headers
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
Connection: keep-alive
Content-Length: 21
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: exemple.com:3000
Origin: http://randomsite.com
Referer: http://random.site.com/
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36
//Form Data
query: random search
And in the preview tab I can see all my 10 results in json.
I want my api to accessed only from exemple.com, can someone help me on that ?
But in the network tab, I have the response with a 200 status and all the results.
How is it possible to have the error and the result at the same time ?
The Same Origin Policy prevents JavaScript (embedded in a web page on a different origin) from reading the data in the response.
It is enforced by the browser.
It doesn't prevent the request being made.
It doesn't prevent the browser from getting the response.
It doesn't prevent the owner of the browser from seeing the response through other means (such as the Network tab) and even if it did, they could just make the HTTP request using another tool, such as cURL.
CORS allows a server to tell the browser to relax the Same Origin Policy and allow the JavaScript access to the data.
I want my api to accessed only from exemple.com, can someone help me on that ?
This isn't possible.
JavaScript on other origins won't be able to read the data directly, but it does nothing for non-JS approaches or indirect (e.g. via a proxy) approaches.

Proxied http response fails to set cookie

I'm developing a web application and testing it using Google Chrome 60.0.3112.113.
To simplify the development process I use a node.js development web server with http-proxy-middleware to proxy my API request to the backend.
Now when I send a HTTP POST request using axios to one of the API endpoints to create a session in my backend, I get back slightly altered responses headers (copied from DevTools):
Direct response
HTTP/1.1 200 OK
Content-Length: 122
Content-Type: application/json
Set-Cookie: sessionid={4621f755-37da-41da-bdbd-9a6ce0ee02b7}; Max-Age=31536000; Version=1
Proxied response
HTTP/1.1 200 OK
X-Powered-By: Express
connection: close
content-length: 122
content-type: application/json
set-cookie: sessionid={4621f755-37da-41da-bdbd-9a6ce0ee02b7}; Max-Age=31536000; Version=1
Date: Thu, 07 Sep 2017 11:06:43 GMT
The problem is that chrome doesn't set the cookie specified in the proxied response (DevTools->Application->Storage->Cookies stays empty), however the direct response sets the cookie as expected.
Cookies are shown correctly in DevTools->Network->My Request->Cookies.
Both versions (direct and proxied) are being accessed via http://localhost:[8080 / 3000]
Could the lowercase set-cookie header be ignored in chrome?
Or could the other headers interfere with setting of the cookie?
Btw: Works fine in Safari 10.1.2 (12603.3.8)
// proxy middleware options
var options = {
target: 'http://localhost:8081', // target host
changeOrigin: true, // needed for virtual hosted sites
ws: true, // proxy websockets
logLevel: "debug",
pathRewrite: {
'^/src/api/' : '/api/'
},
onProxyRes: function (proxyRes, req, res) {
if (proxyRes.headers['set-cookie'] != undefined) {
req.session['cookie'] = proxyRes.headers['set-cookie']; // must be or you will get new session for each call
req.session['proxy-cookie'] = proxyRes.headers['set-cookie']; // add to other key because cookie will be lost
}
console.log("response: " + req.session.id);
console.log(req.session);
},
onProxyReq: function (proxyReq, req, res) {
// check for whether the session be freshed
if (req.session.view)
req.session.view ++;
else
req.session.view = 1;
// use ower key to restore cookie
if (req.session['proxy-cookie'] != undefined)
proxyReq.setHeader('cookie', req.session['proxy-cookie'][0]);
console.log("request: " + req.session.id);
console.log(req.session);
}
};
Set cookieDomainRewrite: 'localhost'

CORS - OPTIONS ... 404 (not found)

Hi I'm having a problem with CORS.
GENERAL STRUCTURE:
Angular 4 sends data of a Form to my api.
Function saveForm() is executed when I send information about form contact.
app.component.ts
saveForm() {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
let requestOptions = new RequestOptions({ headers: headers });
// Encode JSON as string to exchange data to web server.
this.data = JSON.stringify(this.myContact);
this.http.post('https://pablocordovaupdated.herokuapp.com/api', this.data, requestOptions).subscribe(res => {
let result = res.json();
if(result['result'] == 'success') {
this.successMessage = true;
}
}
);
}
Here is the root of problem, because I'm using POST and Content-Type->application/json to send my data and by definition It gives me a problem with CORS - preflighted request, here more definition: Link about preflighted request
This meaning that before angular 4 sends the data to the server, this asks to server if it is available to receive my data with the verb OPTION, if the response of server is OK then angular 4 sends the data of form.
PROBLEM:
In console I always get this error message:
OPTIONS https://pablocordovaupdated.herokuapp.com/api 404 (Not Found)
XMLHttpRequest cannot load
https://pablocordovaupdated.herokuapp.com/api. Response to preflight
request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'https://pablocordova.herokuapp.com' is therefore not
allowed access. The response had HTTP status code 404.
XHR failed loading: POST
"https://pablocordovaupdated.herokuapp.com/api".
ERROR Response {_body: ProgressEvent, status: 0, ok: false,
statusText: "", headers: Headers…}
WHAT I TRIED:
so far, I understand that problem is because https://pablocordovaupdated.herokuapp.com/api doesn't return answer for verb OPTIONS
then I tried to resolve this of 2 ways:
Using cors package CORS package
I configured according documentation.
var express = require('express');
var cors = require('cors');
...
var app = express();
...
app.options('/api', cors());
app.post('/api', cors(), function(req, res, next) {
// Proccess to send this data via email
// and also save in data base(only for learning)
});
But I get in console the same error.
Configuring headers manually by Discussion StackOverFlow or Discussion Github
That code is inserted inside every route that I want to write the headers, in my case:
app.post('/api', function(req, res, next) {
if (req.method === 'OPTIONS') {
console.log('!OPTIONS');
var headers = {};
// IE8 does not allow domains to be specified, just the *
// headers["Access-Control-Allow-Origin"] = req.headers.origin;
headers["Access-Control-Allow-Origin"] = "*";
headers["Access-Control-Allow-Methods"] = "POST, GET, PUT, DELETE, OPTIONS";
headers["Access-Control-Allow-Credentials"] = false;
headers["Access-Control-Max-Age"] = '86400'; // 24 hours
headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept";
res.writeHead(200, headers);
res.end();
} else {
// Process to send this data via email
// and also save in data base(only for learning)
}
});
Here the problem is that never execute console.log('!OPTIONS');
Here also I tried simply:
app.post('/api', function(req, res, next) {
console.log('!I AM YOUR FATHER');
...
});
but never is printed.
Note: I tried to see this message with heroku logs because the whole page is in Heroku.
But also doing this I get the same error.
MORE INFORMATION:
When the .../api is called, I have this headers
**GENERAL**:
Request URL:https://pablocordovaupdated.herokuapp.com/api
Request Method:OPTIONS
Status Code:404 Not Found
Remote Address:23.21.185.158:443
Referrer Policy:no-referrer-when-downgrade
**RESPONSE HEADERS**:
HTTP/1.1 404 Not Found
Connection: keep-alive
Server: Cowboy
Date: Wed, 10 May 2017 04:14:45 GMT
Content-Length: 494
Content-Type: text/html; charset=utf-8
Cache-Control: no-cache, no-store
**REQUEST HEADERS**:
OPTIONS /api HTTP/1.1
Host: pablocordovaupdated.herokuapp.com
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: POST
Origin: https://pablocordova.herokuapp.com
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36
Access-Control-Request-Headers: content-type
Accept: */*
Referer: https://pablocordova.herokuapp.com/
Accept-Encoding: gzip, deflate, sdch, br
Accept-Language: es-ES,es;q=0.8
QUESTION:
That is my true problem or I am understanding bad? and/or
What can I do to solve that problem? and/or
Any advices?
Thanks.
The problem seems to be: https://pablocordovaupdated.herokuapp.com/api always returns 404:
$ curl -i https://pablocordovaupdated.herokuapp.com/api
HTTP/1.1 404 Not Found
That is, the server isn’t doing anything special/different for the OPTIONS request — instead all requests are just hitting that 404.
app.post('/api', function(req, res, next) {
console.log('!I AM YOUR FATHER');
...
});
I’m not super clear on how Express handles this case, but it may be that must configure it to send some kind of response for that route — not just a console.log on the server side.
When I look at the content of https://pablocordovaupdated.herokuapp.com/api what I see is just a generic Heroku 404 page — so it’s not being served from your app but instead falling back to being served by Heroku. For example, the contents of that 404 page have this:
<iframe src="//www.herokucdn.com/error-pages/no-such-app.html"></iframe>
That is, it appears to be that request for that URL are never getting handled as expected by your app code at all under any circumstances. So it seems like that’s what you need to fix first.

Resources