Cross-Origin Resource Sharing CORS
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
Origin includes the combination of protocol, domain, and port. This means
- https://api.mydomain.com and
- https://mydomain.com
are actually different origins and thus impacted by same-origin policy.
In a similar way,
- http://localhost:9000 and
- http://localhost:8080
are also different origins. The path or query parameters are ignored when considering the origin.
The reason for The Same-Origin Policy
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.
- Let’s say you browse to a malicious website https://evilunicorns.com while logged into https://examplebank.com.
- Without same-origin policy, that hacker website could make authenticated malicious AJAX calls to https://examplebank.com/api to
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).
The Reasons for Cross-Origin Resource Sharing
There are legitimate reasons for a website to make cross-origin HTTP requests:
- Maybe a single-page app at https://mydomain.com needs to make AJAX calls to https://api.mydomain.com;
- or maybe https://mydomain.com incorporates some 3rd party fonts or analytics providers like Google Analytics or MixPanel.
- Cross-Origin Resource Sharing (CORS) enables these cross-domain requests.
How CORS works
This is how a simple CORS request works:
-
A browser tab open to
https://www.mydomain.com
initiates AJAX requestGET https://api.mydomain.com/widgets
-
Along with adding headers like
Host
, the browser automatically adds theOrigin
Request Header for cross-origin requests:
GET /widgets/ HTTP/1.1
Host: api.mydomain.com
Origin: https://www.mydomain.com
[Rest of request...]
- The server checks the
Origin
request header. If the Origin value is allowed, it sets theAccess-Control-Allow-Origin
to the value in the request headerOrigin
.
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://www.mydomain.com
Content-Type: application/json
[Rest of response...]
- When the browser receives the response, the browser checks the
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 theAccess-Control-Allow-Origin
matches the single origin exactly or contains the wildcard * operator.- A server that responds
Access-Control-Allow-Origin: *
allows all origins which can be a large security risk. - Only use * if your application absolutely requires it such as creating an open/public API.
- A server that responds
The CORS npm module
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
.
- With
same-origin
you can perform requests only to yourorigin
, otherwise the request will result in an error. - With
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
- setorigin
totrue
to reflect the request origin, as defined byreq.header('Origin')
, or set it tofalse
to disable CORS.String
- setorigin
to a specific origin. For example if you set it to"http://example.com"
only requests from “http://example.com” will be allowed.RegExp
- setorigin
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
- setorigin
to an array of valid origins. Each origin can be aString
or aRegExp
. For example["http://example1.com", /\.example2\.com$/]
will accept any request from “http://example1.com” or from a subdomain of “example2.com”.Function
- setorigin
to a function implementing some custom logic. The function takes the request origin as the first parameter and a callback (which expects the signatureerr [object], allow [bool]
) as the second.
Ejemplo: server en ULL-MII-SYTWS-1920/food-lookup-demo
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
});
Cómo hacer un Ataque CSRF
CORS references
- Authoritative guide to CORS (Cross-Origin Resource Sharing) for REST APIs
- Using CORS in Express by Alexis Hevia
- fetch
- cors is a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options.
- Multiple Ways of API Integration in your JAMStack
- Associated GitHub repo https://github.com/cfjedimaster/jamstack_api_approaches