17 Jan, 2017
First, install basic-auth-connect
.
Here is the server code configured with username
, password
, port
and validOrigins
(which should be a list of origins where you want to allow to make CORS requests):
const express = require("express");
const basicAuth = require("basic-auth-connect");
const username = "username";
const password = "password";
const port = "8080";
const validOrigins = ["http://localhost:8000"]; // Can"t use * when using credentials
let app = express();
// Middleware
app.use((req, res, next) => {
let validOrigin = false;
validOrigins.forEach((origin) => {
if (origin === req.get("origin")) {
validOrigin = true;
}
});
if (validOrigin) {
res.header("Access-Control-Allow-Origin", req.get("origin"));
res.header("Access-Control-Request-Method", "POST, GET, PUT, DELETE");
res.header("Access-Control-Allow-Headers", "Authorization");
res.header("Access-Control-Allow-Credentials", "true");
}
if (req.method === "OPTIONS") {
res.json({preflight: true});
} else {
next();
}
});
// Must come after the CORS middleware above
app.use(basicAuth(username, password));
app.get("/api", (req, res) => {
res.json({api: true});
});
const server = app.listen(port, () => {
console.log("App listening on port %s", server.address().port);
console.log("Press Ctrl+C to quit.");
});
When you run this you'll get a server on port 8080 which responds with CORS headers. On the CORS pre-flight OPTIONS request, it will return with the JSON {preflight: true}
. On the subsequent GET, if the username and password are set, you'll see {api: true}
.
To run this from a client you'll need this index.html
file served somewhere:
<html>
<body>
<div id="msg">Running ...</div>
<script>
const username = "username";
const password = "password";
const url = "http://localhost:8080/api";
const headers = new Headers();
const creds = btoa(username + ":" + password);
headers.append("Authorization", "Basic " + creds );
const options = {
method: "GET",
headers: headers,
credentials: "include",
};
console.log(`Making request to URL with ${creds}`);
fetch(url, options)
.then(function(response) {
return response.json();
})
.then(function(json) {
console.log(json);
document.getElementById("msg").innerHTML = "Success, see console log.";
}).catch((error) => {
console.error(error);
document.getElementById("msg").innerHTML = "Failed, see console log.";
});
</script>
</body>
</html>
You can't just load it into Chrome locally because you'll get:
Fetch API cannot load http://localhost:8080/api/. Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'http://localhost:8000' that is not equal to the supplied origin. Origin 'null' is therefore not allowed access. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
This isn't really a CORS problem, it is a security feature of Chrome because it treats the origin as null
for local files and won't make CORS requests. Instead you need to access your index.html
file from a real hosted URL.
You can do this easily by running this command in the same directory as the index.html
file:
python -m SimpleHTTPServer
Now visit http://localhost:8000 everything should work!
This example also works on Firefox, and if you include this polyfill it will work on Safari too:
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.1/fetch.js"></script>
Update:
Here's another version that use node:
function handler (request, response) {
let validOrigin = false
validOrigins.forEach((origin) => {
if (origin === request.headers.origin) {
validOrigin = true
}
})
if (validOrigin) {
response.setHeader('Access-Control-Allow-Origin', request.headers.origin)
response.setHeader('Access-Control-Request-Method', 'POST, GET, PUT, DELETE')
response.setHeader('Access-Control-Allow-Headers', 'Authorization')
response.setHeader('Access-Control-Allow-Credentials', 'true')
}
if (request.method === 'OPTIONS') {
response.writeHead(200, {'Content-Type': 'application/json'})
response.write(JSON.stringify({preflight: true}, null, 4))
response.end()
return
}
if (request.url === '/api/cors') {
response.writeHead(200, {'Content-Type': 'application/json'})
response.write(JSON.stringify({cors: true}, null, 4))
response.end()
return
}
// ... Carry on as usual
}
And a test:
const assert = require('assert')
const server = require('./server')
const TEST_ORIGIN = 'http://...example.com'
server.listen(8080, () => {
console.log('Server is listening')
const valid = fetchHTTP({
url: 'http://localhost:8080/api/cors',
headers: {'Origin': TEST_ORIGIN}
})
.then((spec) => {
const {response} = spec
assert.equal(response.headers['access-control-allow-origin'], TEST_ORIGIN)
assert.equal(response.headers['access-control-request-method'], 'POST, GET, PUT, DELETE')
assert.equal(response.headers['access-control-allow-headers'], 'Authorization')
assert.equal(response.headers['access-control-allow-credentials'], 'true')
console.log('Passed valid')
})
.catch((error) => {
console.error('Valid test FAILED', error)
console.log(JSON.stringify(error, null, 4))
})
const invalid = fetchHTTP({
url: 'http://localhost:8080/api/cors',
headers: {'Origin': 'http://stolen-origin.example.com'}
})
.then((spec) => {
const {response} = spec
assert(!('access-control-allow-origin' in response.headers))
assert(!('access-control-request-method' in response.headers))
assert(!('access-control-allow-headers' in response.headers))
assert(!('access-control-allow-credentials' in response.headers))
console.log('Passed invalid')
})
.catch((error) => {
console.error('Invalid test FAILED', error)
console.log(JSON.stringify(error, null, 4))
})
Promise.all([
valid,
invalid
])
.then((res) => {
console.log('All done')
server.close()
})
})
Copyright James Gardner 1996-2020 All Rights Reserved. Admin.