3º. 2º cuatrimestre. Itinerario de Tecnologías de la Información. Grado en Ingeniería Informática. Curso 2019/2020
CORS is a security mechanism that allows a web page from one domain or Origin to access a resource with a different domain (a cross-domain request).
CORS is a relaxation of the same-origin policy implemented in modern browsers. Without features like CORS, websites are restricted to accessing resources from the same origin through what is known as same-origin policy.
Origin includes the combination of protocol, domain, and port. This means
are actually different origins and thus impacted by same-origin policy.
In a similar way,
are also different origins. The path or query parameters are ignored when considering the origin.
You, like many websites, may use cookies to keep track of authentication or session info. Those cookies are bounded to a certain domain when they are created. On every HTTP call to that domain, the browser will attach the cookies that were created for that domain. This is on every HTTP call, which could be for static images, HTML pages, or even AJAX calls.
This means when you log into https://examplebank.com, a cookie is stored for https://examplebank.com. If that bank is a single-page React app, they may have created a REST API at https://examplebank.com/api for the SPA to communicate via AJAX.
POST /withdraw
even though the hacker website doesn’t have direct access to the bank’s cookies.This is due to the browser behavior of automatically attaching any cookies bounded to https://examplebank.com for any HTTP calls to that domain, including AJAX calls from https://evilunicorns.com to https://examplebank.com.
By restricting HTTP calls to only ones to the same origin (i.e. the browser tab’s domain), same-origin policy closes some hacker backdoors such as around Cross-Site Request Forgery (CSRF) (Although not all. Mechanisms like CSRF tokens are still necessary).
There are legitimate reasons for a website to make cross-origin HTTP requests:
This is how a simple CORS request works:
A browser tab open to https://www.mydomain.com
initiates AJAX request GET https://api.mydomain.com/widgets
Along with adding headers like Host
, the browser automatically adds the Origin
Request Header for cross-origin requests:
GET /widgets/ HTTP/1.1
Host: api.mydomain.com
Origin: https://www.mydomain.com
[Rest of request...]
Origin
request header. If the Origin value is allowed, it sets the Access-Control-Allow-Origin
to the value in the request header Origin
. HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.mydomain.com
Content-Type: application/json
[Rest of response...]
Access-Control-Allow-Origin
header to see if it matches the origin of the tab. If not, the response is blocked. The check passes such as in this example if either the Access-Control-Allow-Origin
matches the single origin exactly or contains the wildcard * operator.
Access-Control-Allow-Origin: *
allows all origins which can be a large security risk.If you want to avoid the blocking, the server that hosts the resource needs to have CORS enabled.
What you can do on the client side (and probably what you are thinking of) is set the mode of fetch
to CORS
(although this is the default setting I believe):
fetch(request, {mode: 'cors'});
The mode
option specifies the mode you want to use for the request, e.g., cors
, no-cors
, or same-origin
.
same-origin
you can perform requests only to your origin
, otherwise the request will result in an error.no-cors
, you can perform requests to other origins, even if they don’t set the required CORS headers, but you’ll get an opaque response. An opaque response is for a request made for a resource on a different origin that doesn’t return CORS headers. With an opaque response we won’t be able to read the data returned or view the status of the request, meaning we can’t check if the request was successful or not.However this still requires the server to enable CORS as well, and allow your domain to request the resource.
In Express we can use the module cors
$ npm install cors
If inside the app we use this middleware:
var express = require('express')
var cors = require('cors')
var app = express()
app.use(cors())
app.get('/products/:id', function (req, res, next) {
res.json({msg: 'This is CORS-enabled for all origins!'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})
To enable CORS for a Single Route we do:
var express = require('express')
var cors = require('cors')
var app = express()
app.get('/products/:id', cors(), function (req, res, next) {
res.json({msg: 'This is CORS-enabled for a Single Route'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})
We can configure CORS:
var express = require('express')
var cors = require('cors')
var app = express()
var corsOptions = {
origin: 'http://example.com',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}
app.get('/products/:id', cors(corsOptions), function (req, res, next) {
res.json({msg: 'This is CORS-enabled for only example.com.'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})
The origin
option used in this example configures the Access-Control-Allow-Origin CORS header.
Possible values:
Boolean
- set origin
to true
to reflect the request origin, as defined by req.header('Origin')
, or set it to false
to disable CORS.String
- set origin
to a specific origin. For example if you set it to "http://example.com"
only requests from “http://example.com” will be allowed.RegExp
- set origin
to a regular expression pattern which will be used to test the request origin. If it’s a match, the request origin will be reflected. For example the pattern /example\.com$/
will reflect any request that is coming from an origin ending with “example.com”.Array
- set origin
to an array of valid origins. Each origin can be a String
or a RegExp
. For example ["http://example1.com", /\.example2\.com$/]
will accept any request from “http://example1.com” or from a subdomain of “example2.com”.Function
- set origin
to a function implementing some custom logic. The function takes the request origin as the first parameter and a callback (which expects the signature err [object], allow [bool]
) as the second.Para entender mejor esta sección
Véase la rama/branch: 10-crguezl-master-cors-01
del repositorio
ULL-MII-SYTWS-1920/food-lookup-demo
Si en Client-js
cambiamos el fetch
para solicitar al server en 3001 que es donde escucha nuestro servidor:
function search(query, cb) {
return fetch(`http://localhost:3001/api/food?q=${query}`, {
accept: "application/json"
})
.then(checkStatus)
.then(parseJSON)
.then(cb);
}
Obtenemos una respuesta similar a esta:
Access to fetch at
http://localhost:3001/api/food?q=r
from originhttp://localhost:3000
has been blocked by CORS policy:
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.
localhost/:1
Uncaught (in promise) TypeError: Failed to fetch
Usando el middleware cors
arreglamos el problema:
const express = require("express");
const fs = require("fs");
const sqlite = require("sql.js");
const cors = require("cors");
const filebuffer = fs.readFileSync("db/usda-nnd.sqlite3");
const db = new sqlite.Database(filebuffer);
const app = express();
app.set("port", process.env.PORT || 3001);
// Express only serves static assets in production
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
}
const COLUMNS = [
"carbohydrate_g",
"protein_g",
"fa_sat_g",
"fa_mono_g",
"fa_poly_g",
"kcal",
"description"
];
const corsOptions = {
origin: 'http://localhost:3000',
optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204
}
app.get("/api/food", cors(corsOptions), (req, res) => {
const param = req.query.q;
if (!param) {
res.json({
error: "Missing required parameter `q`"
});
return;
}
// WARNING: Not for production use! The following statement
// is not protected against SQL injections.
const r = db.exec(
`
select ${COLUMNS.join(", ")} from entries
where description like '%${param}%'
limit 100
`
);
if (r[0]) {
res.json(
r[0].values.map(entry => {
const e = {};
COLUMNS.forEach((c, idx) => {
// combine fat columns
if (c.match(/^fa_/)) {
e.fat_g = e.fat_g || 0.0;
e.fat_g = (parseFloat(e.fat_g, 10) +
parseFloat(entry[idx], 10)).toFixed(2);
} else {
e[c] = entry[idx];
}
});
return e;
})
);
} else {
res.json([]);
}
});
app.listen(app.get("port"), () => {
console.log(`Find the server at: http://localhost:${app.get("port")}/`); // eslint-disable-line no-console
});