Skip to content
Workers
Visit Workers on GitHub
Set theme to dark (⇧+D)

Examples

Shows how to restrict access using the HTTP "Basic" schema.

/**
* Shows how to restrict access using the HTTP "Basic" schema.
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication
* @see https://tools.ietf.org/html/rfc7617
*
* A user-id containing a colon (":") character is invalid, as the
* first colon in a user-pass string separates user and password.
*/
const BASIC_USER = 'admin'
const BASIC_PASS = 'admin'
/**
* Receives a HTTP request and replies with a response.
* @param {Request} request
* @returns {Promise<Response>}
*/
async function handleRequest(request) {
const { protocol, pathname } = new URL(request.url)
// In the case of a "Basic" authentication, the exchange
// MUST happen over an HTTPS (TLS) connection to be secure.
if ('https:' !== protocol || 'https' !== request.headers.get('x-forwarded-proto')) {
throw new BadRequestException('Please use a HTTPS connection.')
}
switch (pathname) {
case '/':
return new Response('Anyone can access the homepage.')
case '/logout':
// Invalidate the "Authorization" header by returning a HTTP 401.
// We do not send a "WWW-Authenticate" header, as this would trigger
// a popup in the browser, immediately asking for credentials again.
return new Response('Logged out.', { status: 401 })
case '/admin': {
// The "Authorization" header is sent when authenticated.
if (request.headers.has('Authorization')) {
// Throws exception when authorization fails.
const { user, pass } = basicAuthentication(request)
verifyCredentials(user, pass)
// Only returns this response when no exception is thrown.
return new Response('You have private access.', {
status: 200,
headers: {
'Cache-Control': 'no-store'
}
})
}
// Not authenticated.
return new Response('You need to login.', {
status: 401,
headers: {
// Prompts the user for credentials.
'WWW-Authenticate': 'Basic realm="my scope", charset="UTF-8"'
}
})
}
case '/favicon.ico':
case '/robots.txt':
return new Response(null, { status: 204 })
}
return new Response('Not Found.', { status: 404 })
}
/**
* Throws exception on verification failure.
* @param {string} user
* @param {string} pass
* @throws {UnauthorizedException}
*/
function verifyCredentials(user, pass) {
if (BASIC_USER !== user) {
throw new UnauthorizedException('Invalid username.')
}
if (BASIC_PASS !== pass) {
throw new UnauthorizedException('Invalid password.')
}
}
/**
* Parse HTTP Basic Authorization value.
* @param {Request} request
* @throws {BadRequestException}
* @returns {{ user: string, pass: string }}
*/
function basicAuthentication(request) {
const Authorization = request.headers.get('Authorization')
const [scheme, encoded] = Authorization.split(' ')
// The Authorization header must start with "Basic", followed by a space.
if (!encoded || scheme !== 'Basic') {
throw new BadRequestException('Malformed authorization header.')
}
// Decodes the base64 value and performs unicode normalization.
// @see https://datatracker.ietf.org/doc/html/rfc7613#section-3.3.2 (and #section-4.2.2)
// @see https://dev.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/normalize
const decoded = atob(encoded).normalize()
// The username & password are split by the first colon.
//=> example: "username:password"
const index = decoded.indexOf(':')
// The user & password are split by the first colon and MUST NOT contain control characters.
// @see https://tools.ietf.org/html/rfc5234#appendix-B.1 (=> "CTL = %x00-1F / %x7F")
if (index === -1 || /[\0-\x1F\x7F]/.test(decoded)) {
throw new BadRequestException('Invalid authorization value.')
}
return {
user: decoded.substring(0, index),
pass: decoded.substring(index + 1),
}
}
function UnauthorizedException(reason) {
this.status = 401
this.statusText = 'Unauthorized'
this.reason = reason
}
function BadRequestException(reason) {
this.status = 400
this.statusText = 'Bad Request'
this.reason = reason
}
addEventListener('fetch', event => {
event.respondWith(
handleRequest(event.request).catch(err => {
const message = err.reason || err.stack || 'Unknown Error'
return new Response(message, {
status: err.status || 500,
statusText: err.statusText || null,
headers: {
'Content-Type': 'text/plain;charset=UTF-8',
// Disables caching by default.
'Cache-Control': 'no-store',
// Returns the "Content-Length" header for HTTP HEAD requests.
'Content-Length': message.length,
}
})
})
)
})

Cache POST requests using the Cache API.

async function sha256(message) {
// encode as UTF-8
const msgBuffer = new TextEncoder().encode(message)
// hash the message
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer)
// convert ArrayBuffer to Array
const hashArray = Array.from(new Uint8Array(hashBuffer))
// convert bytes to hex string
const hashHex = hashArray.map(b => ("00" + b.toString(16)).slice(-2)).join("")
return hashHex
}
async function handlePostRequest(event) {
const request = event.request
const body = await request.clone().text()
// Hash the request body to use it as a part of the cache key
const hash = await sha256(body)
const cacheUrl = new URL(request.url)
// Store the URL in cache by prepending the body's hash
cacheUrl.pathname = "/posts" + cacheUrl.pathname + hash
// Convert to a GET to be able to cache
const cacheKey = new Request(cacheUrl.toString(), {
headers: request.headers,
method: "GET",
})
const cache = caches.default
// Find the cache key in the cache
let response = await cache.match(cacheKey)
// Otherwise, fetch response to POST request from origin
if (!response) {
response = await fetch(request)
event.waitUntil(cache.put(cacheKey, response.clone()))
}
return response
}
addEventListener("fetch", event => {
try {
const request = event.request
if (request.method.toUpperCase() === "POST")
return event.respondWith(handlePostRequest(event))
return event.respondWith(fetch(request))
} catch (e) {
return event.respondWith(new Response("Error thrown " + e.message))
}
})

Return a response based on the incoming request's URL, HTTP method, User Agent, IP address, ASN or device type.

const BLOCKED_HOSTNAMES = ["nope.mywebsite.com", "bye.website.com"]
async function handleRequest(request) {
// Return a new Response based on a URL's hostname
const url = new URL(request.url)
if (BLOCKED_HOSTNAMES.includes(url.hostname)) {
return new Response("Blocked Host", { status: 403 })
}
// Block paths ending in .doc or .xml based on the URL's file extension
const forbiddenExtRegExp = new RegExp(/\.(doc|xml)$/)
if (forbiddenExtRegExp.test(url.pathname)) {
return new Response("Blocked Extension", { status: 403 })
}
// On HTTP method
if (request.method === "POST") {
return new Response("Response for POST")
}
// On User Agent
const userAgent = request.headers.get("User-Agent") || ""
if (userAgent.includes("bot")) {
return new Response("Block User Agent containing bot", { status: 403 })
}
// On Client's IP address
const clientIP = request.headers.get("CF-Connecting-IP")
if (clientIP === "1.2.3.4") {
return new Response("Block the IP 1.2.3.4", { status: 403 })
}
// On ASN
if (request.cf && request.cf.asn == 64512) {
return new Response("Block the ASN 64512 response")
}
// On Device Type
// Requires Enterprise "CF-Device-Type Header" zone setting or
// Page Rule with "Cache By Device Type" setting applied.
const device = request.headers.get("CF-Device-Type")
if (device === "mobile") {
return Response.redirect("https://mobile.example.com")
}
console.error(
"Getting Client's IP address, device type, and ASN are not supported in playground. Must test on a live worker",
)
return fetch(request)
}
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})

Add the necessary CORS headers to a third party API response.

// We support the GET, POST, HEAD, and OPTIONS methods from any origin,
// and allow any header on requests. These headers must be present
// on all responses to all CORS preflight requests. In practice, this means
// all responses to OPTIONS requests.
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
"Access-Control-Max-Age": "86400",
}
// The URL for the remote third party API you want to fetch from
// but does not implement CORS
const API_URL = "https://examples.cloudflareworkers.com/demos/demoapi"
// The endpoint you want the CORS reverse proxy to be on
const PROXY_ENDPOINT = "/corsproxy/"
// The rest of this snippet for the demo page
function rawHtmlResponse(html) {
return new Response(html, {
headers: {
"content-type": "text/html;charset=UTF-8",
},
})
}
const DEMO_PAGE = `
<!DOCTYPE html>
<html>
<body>
<h1>API GET without CORS Proxy</h1>
<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful">Shows TypeError: Failed to fetch since CORS is misconfigured</a>
<p id="noproxy-status"/>
<code id="noproxy">Waiting</code>
<h1>API GET with CORS Proxy</h1>
<p id="proxy-status"/>
<code id="proxy">Waiting</code>
<h1>API POST with CORS Proxy + Preflight</h1>
<p id="proxypreflight-status"/>
<code id="proxypreflight">Waiting</code>
<script>
let reqs = {};
reqs.noproxy = () => {
return fetch("${API_URL}").then(r => r.json())
}
reqs.proxy = async () => {
let href = "${PROXY_ENDPOINT}?apiurl=${API_URL}"
return fetch(window.location.origin + href).then(r => r.json())
}
reqs.proxypreflight = async () => {
let href = "${PROXY_ENDPOINT}?apiurl=${API_URL}"
let response = await fetch(window.location.origin + href, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
msg: "Hello world!"
})
})
return response.json()
}
(async () => {
for (const [reqName, req] of Object.entries(reqs)) {
try {
let data = await req()
document.getElementById(reqName).innerHTML = JSON.stringify(data)
} catch (e) {
document.getElementById(reqName).innerHTML = e
}
}
})()
</script>
</body>
</html>`
async function handleRequest(request) {
const url = new URL(request.url)
let apiUrl = url.searchParams.get("apiurl")
if (apiUrl == null) {
apiUrl = API_URL
}
// Rewrite request to point to API url. This also makes the request mutable
// so we can add the correct Origin header to make the API server think
// that this request isn't cross-site.
request = new Request(apiUrl, request)
request.headers.set("Origin", new URL(apiUrl).origin)
let response = await fetch(request)
// Recreate the response so we can modify the headers
response = new Response(response.body, response)
// Set CORS headers
response.headers.set("Access-Control-Allow-Origin", url.origin)
// Append to/Add Vary header so browser will cache response correctly
response.headers.append("Vary", "Origin")
return response
}
function handleOptions(request) {
// Make sure the necessary headers are present
// for this to be a valid pre-flight request
let headers = request.headers;
if (
headers.get("Origin") !== null &&
headers.get("Access-Control-Request-Method") !== null &&
headers.get("Access-Control-Request-Headers") !== null
){
// Handle CORS pre-flight request.
// If you want to check or reject the requested method + headers
// you can do that here.
let respHeaders = {
...corsHeaders,
// Allow all future content Request headers to go back to browser
// such as Authorization (Bearer) or X-Client-Name-Version
"Access-Control-Allow-Headers": request.headers.get("Access-Control-Request-Headers"),
}
return new Response(null, {
headers: respHeaders,
})
}
else {
// Handle standard OPTIONS request.
// If you want to allow other HTTP Methods, you can do that here.
return new Response(null, {
headers: {
Allow: "GET, HEAD, POST, OPTIONS",
},
})
}
}
addEventListener("fetch", event => {
const request = event.request
const url = new URL(request.url)
if(url.pathname.startsWith(PROXY_ENDPOINT)){
if (request.method === "OPTIONS") {
// Handle CORS preflight requests
event.respondWith(handleOptions(request))
}
else if(
request.method === "GET" ||
request.method === "HEAD" ||
request.method === "POST"
){
// Handle requests to the API server
event.respondWith(handleRequest(request))
}
else {
event.respondWith(
new Response(null, {
status: 405,
statusText: "Method Not Allowed",
}),
)
}
}
else {
// Serve demo page
event.respondWith(rawHtmlResponse(DEMO_PAGE))
}
})

Protect sensitive data to prevent data loss, and send alerts to a webhooks server in the event of a data breach.

const DEBUG = true
const SOME_HOOK_SERVER = "https://webhook.flow-wolf.io/hook"
/**
* Alert a data breach by posting to a webhook server
*/
function postDataBreach(request) {
return fetch(SOME_HOOK_SERVER, {
method: "POST",
body: JSON.stringify(body),
headers: {
"content-type": "application/json;charset=UTF-8"
},
body: JSON.stringify({
ip: request.headers.get("cf-connecting-ip"),
time: Date.now(),
request: request
})
})
}
/**
* Define personal data with regular expressions.
* Respond with block if credit card data, and strip
* emails and phone numbers from the response.
* Execution will be limited to MIME type "text/*".
*/
async function handleRequest(request) {
const response = await fetch(request)
// Return origin response, if response wasn’t text
const contentType = response.headers.get("content-type") || ""
if (!contentType.toLowerCase().includes("text/")) {
return response
}
let text = await response.text()
// When debugging replace the response
// from the origin with an email
text = DEBUG
? text.replace("You may use this", "me@example.com may use this")
: text
const sensitiveRegexsMap = {
creditCard: String.raw`\b(?:4[0-9]{12}(?:[0-9]{3})?|(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\d{3})\d{11})\b`,
email: String.raw`\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b`,
phone: String.raw`\b07\d{9}\b`
}
for (const kind in sensitiveRegexsMap) {
const sensitiveRegex = new RegExp(sensitiveRegexsMap[kind], "ig")
const match = await sensitiveRegex.test(text)
if (match) {
// Alert a data breach
await postDataBreach(request)
// Respond with a block if credit card,
// otherwise replace sensitive text with `*`s
return kind === "creditCard"
? new Response(kind + " found\nForbidden\n", {
status: 403,
statusText: "Forbidden"
})
: new Response(text.replace(sensitiveRegex, "**********"), response)
}
}
return new Response(text, response)
}
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})

Fetch weather data from an API using the user's geolocation data.

addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
let endpoint = "https://api.waqi.info/feed/geo:"
const token = "" //Use a token from https://aqicn.org/api/
let html_style = `body{padding:6em; font-family: sans-serif;} h1{color:#f6821f}`
let html_content = "<h1>Weather 🌦</h1>"
latitude = request.cf.latitude
longitude = request.cf.longitude
endpoint+= `${latitude};${longitude}/?token=${token}`
const init = {
headers: {
"content-type": "application/json;charset=UTF-8",
},
}
const response = await fetch(endpoint,init)
const content = await response.json()
html_content += `<p>This is a demo using Workers geolocation data. </p>`
html_content += `You are located at: ${latitude},${longitude}.</p>`
html_content += `<p>Based off sensor data from <a href="${content.data.city.url}">${content.data.city.name}</a>:</p>`
html_content += `<p>The AQI level is: ${content.data.aqi}.</p>`
html_content += `<p>The N02 level is: ${content.data.iaqi.no2.v}.</p>`
html_content += `<p>The O3 level is: ${content.data.iaqi.o3.v}.</p>`
html_content += `<p>The temperature is: ${content.data.iaqi.t.v}°C.</p>`
let html = `
<!DOCTYPE html>
<head>
<title>Geolocation: Weather</title>
</head>
<body>
<style>${html_style}</style>
<div id="container">
${html_content}
</div>
</body>`
return new Response(html, {
headers: {
"content-type": "text/html;charset=UTF-8",
},})
}

Personalize website styling based on localized user time.

addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
let grads = [
[{color:"00000c",position:0},{color:"00000c",position:0}],
[{color:"020111",position:85},{color:"191621",position:100}],
[{color:"020111",position:60},{color:"20202c",position:100}],
[{color:"020111",position:10},{color:"3a3a52",position:100}],
[{color:"20202c",position:0},{color:"515175",position:100}],
[{color:"40405c",position:0},{color:"6f71aa",position:80},{color:"8a76ab",position:100}],
[{color:"4a4969",position:0},{color:"7072ab",position:50},{color:"cd82a0",position:100}],
[{color:"757abf",position:0},{color:"8583be",position:60},{color:"eab0d1",position:100}],
[{color:"82addb",position:0},{color:"ebb2b1",position:100}],
[{color:"94c5f8",position:1},{color:"a6e6ff",position:70},{color:"b1b5ea",position:100}],
[{color:"b7eaff",position:0},{color:"94dfff",position:100}],
[{color:"9be2fe",position:0},{color:"67d1fb",position:100}],
[{color:"90dffe",position:0},{color:"38a3d1",position:100}],
[{color:"57c1eb",position:0},{color:"246fa8",position:100}],
[{color:"2d91c2",position:0},{color:"1e528e",position:100}],
[{color:"2473ab",position:0},{color:"1e528e",position:70},{color:"5b7983",position:100}],
[{color:"1e528e",position:0},{color:"265889",position:50},{color:"9da671",position:100}],
[{color:"1e528e",position:0},{color:"728a7c",position:50},{color:"e9ce5d",position:100}],
[{color:"154277",position:0},{color:"576e71",position:30},{color:"e1c45e",position:70},{color:"b26339",position:100}],
[{color:"163C52",position:0},{color:"4F4F47",position:30},{color:"C5752D",position:60},{color:"B7490F",position:80},{color:"2F1107",position:100}],
[{color:"071B26",position:0},{color:"071B26",position:30},{color:"8A3B12",position:80},{color:"240E03",position:100}],
[{color:"010A10",position:30},{color:"59230B",position:80},{color:"2F1107",position:100}],
[{color:"090401",position:50},{color:"4B1D06",position:100}],
[{color:"00000c",position:80},{color:"150800",position:100}],
];
function toCSSGradient(hour)
{
var css = "linear-gradient(to bottom, ";
var data = grads[hour]
var len = data.length;
for (var i=0;i<len;i++)
{
var item = data[i];
css+= " #" + item.color + " " + item.position + "%";
if ( i<len-1 ) css += ",";
}
return css + ")";
}
async function handleRequest(request) {
let html_content = ""
let html_style = `
html{width:100vw; height:100vh;}
body{padding:0; margin:0 !important;height:100%;}
#container {
display: flex;
flex-direction:column;
align-items: center;
justify-content: center;
height: 100%;
color:white;
font-family:sans-serif;
}`
const timezone = request.cf.timezone
let localized_date = new Date(new Date().toLocaleString('en-US', {timeZone: timezone}))
let hour = localized_date.getHours()
let minutes = localized_date.getMinutes()
html_content += "<h1>" + hour + ":"+ minutes + "</h1>"
html_content += "<p>" + timezone + "<br/></p>"
html_style += "body{background:"+toCSSGradient(hour)+";}"
let html = `
<!DOCTYPE html>
<head>
<title>Geolocation: Customized Design</title>
</head>
<body>
<style> ${html_style}</style>
<div id="container">
${html_content}
</div>
</body>`
return new Response(html, {
headers: {
"content-type": "text/html;charset=UTF-8",
},})
}

Create a modified request with edited properties based off of an incoming request.

/**
* Example someHost is set up to return raw JSON
* @param {string} someUrl the URL to send the request to, since we are setting hostname too only path is applied
* @param {string} someHost the host the request will resolve too
*/
const someHost = "example.com"
const someUrl = "https://foo.example.com/api.js"
async function handleRequest(request) {
/**
* The best practice is to only assign new properties on the request
* object (i.e. RequestInit props) using either a method or the constructor
*/
const newRequestInit = {
// Change method
method: "POST",
// Change body
body: JSON.stringify({ bar: "foo" }),
// Change the redirect mode.
redirect: "follow",
// Change headers, note this method will erase existing headers
headers: {
"Content-Type": "application/json",
},
// Change a Cloudflare feature on the outbound response
cf: { apps: false },
}
// Change just the host
const url = new URL(someUrl)
url.hostname = someHost
// Best practice is to always use the original request to construct the new request
// to clone all the attributes. Applying the URL also requires a constructor
// since once a Request has been constructed, its URL is immutable.
const newRequest = new Request(
url.toString(),
new Request(request, newRequestInit),
)
// Set headers using method
newRequest.headers.set("X-Example", "bar")
newRequest.headers.set("Content-Type", "application/json")
try {
return await fetch(newRequest)
} catch (e) {
return new Response(JSON.stringify({ error: e.message }), { status: 500 })
}
}
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})

Serve an HTML form, then read POST requests. Use also to read JSON or POST data from an incoming request.

/**
* rawHtmlResponse returns HTML inputted directly
* into the worker script
* @param {string} html
*/
function rawHtmlResponse(html) {
const init = {
headers: {
"content-type": "text/html;charset=UTF-8",
},
}
return new Response(html, init)
}
/**
* readRequestBody reads in the incoming request body
* Use await readRequestBody(..) in an async function to get the string
* @param {Request} request the incoming request to read from
*/
async function readRequestBody(request) {
const { headers } = request
const contentType = headers.get("content-type") || ""
if (contentType.includes("application/json")) {
return JSON.stringify(await request.json())
}
else if (contentType.includes("application/text")) {
return request.text()
}
else if (contentType.includes("text/html")) {
return request.text()
}
else if (contentType.includes("form")) {
const formData = await request.formData()
const body = {}
for (const entry of formData.entries()) {
body[entry[0]] = entry[1]
}
return JSON.stringify(body)
}
else {
// Perhaps some other type of data was submitted in the form
// like an image, or some other binary data.
return 'a file';
}
}
const someForm = `
<!DOCTYPE html>
<html>
<body>
<h1>Hello World</h1>
<p>This is all generated using a Worker</p>
<form action="/demos/requests" method="post">
<div>
<label for="say">What do you want to say?</label>
<input name="say" id="say" value="Hi">
</div>
<div>
<label for="to">To who?</label>
<input name="to" id="to" value="Mom">
</div>
<div>
<button>Send my greetings</button>
</div>
</form>
</body>
</html>
`
async function handleRequest(request) {
const reqBody = await readRequestBody(request)
const retBody = `The request body sent in was ${reqBody}`
return new Response(retBody)
}
addEventListener("fetch", event => {
const { request } = event
const { url } = request
if (url.includes("form")) {
return event.respondWith(rawHtmlResponse(someForm))
}
if (request.method === "POST") {
return event.respondWith(handleRequest(request))
}
else if (request.method === "GET") {
return event.respondWith(new Response(`The request was a GET`))
}
})

Set common security headers (X-XSS-Protection, X-Frame-Options, X-Content-Type-Options, Permissions-Policy, Referrer-Policy, Strict-Transport-Security, Content-Security-Policy).

const DEFAULT_SECURITY_HEADERS = {
/*
Secure your application with Content-Security-Policy headers.
Enabling these headers will permit content from a trusted domain and all its subdomains.
@see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
"Content-Security-Policy": "default-src 'self' example.com *.example.com",
*/
/*
You can also set Strict-Transport-Security headers.
These are not automatically set because your website might get added to Chrome's HSTS preload list.
Here's the code if you want to apply it:
"Strict-Transport-Security" : "max-age=63072000; includeSubDomains; preload",
*/
/*
Permissions-Policy header provides the ability to allow or deny the use of browser features, such as opting out of FLoC - which you can use below:
"Permissions-Policy": "interest-cohort=()",
*/
/*
X-XSS-Protection header prevents a page from loading if an XSS attack is detected.
@see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection
*/
"X-XSS-Protection": "0; mode=block",
/*
X-Frame-Options header prevents click-jacking attacks.
@see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
*/
"X-Frame-Options": "DENY",
/*
X-Content-Type-Options header prevents MIME-sniffing.
@see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options
*/
"X-Content-Type-Options": "nosniff",
"Referrer-Policy": "strict-origin-when-cross-origin",
'Cross-Origin-Embedder-Policy': 'require-corp; report-to="default";',
'Cross-Origin-Opener-Policy': 'same-site; report-to="default";',
"Cross-Origin-Resource-Policy": "same-site",
}
const BLOCKED_HEADERS = [
"Public-Key-Pins",
"X-Powered-By",
"X-AspNet-Version",
]
addEventListener('fetch', event => {
event.respondWith(addHeaders(event.request))
})
async function addHeaders(req) {
let response = await fetch(req)
let newHeaders = new Headers(response.headers)
const tlsVersion = req.cf.tlsVersion
// This sets the headers for HTML responses:
if (newHeaders.has("Content-Type") && !newHeaders.get("Content-Type").includes("text/html")) {
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders
})
}
Object.keys(DEFAULT_SECURITY_HEADERS).map(function (name) {
newHeaders.set(name, DEFAULT_SECURITY_HEADERS[name]);
})
BLOCKED_HEADERS.forEach(function (name) {
newHeaders.delete(name)
})
if (tlsVersion != "TLSv1.2" && tlsVersion != "TLSv1.3") {
return new Response("You need to use TLS version 1.2 or higher.", { status: 400 })
} else {
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders
})
}
}

Sign and verify a request using the HMAC and SHA-256 algorithms or return a 403.

// We will need some super-secret data to use as a symmetric key.
const encoder = new TextEncoder()
const secretKeyData = encoder.encode("my secret symmetric key")
// Convert a ByteString (a string whose code units are all in the range
// [0, 255]), to a Uint8Array. If you pass in a string with code units larger
// than 255, their values will overflow!
function byteStringToUint8Array(byteString) {
const ui = new Uint8Array(byteString.length)
for (let i = 0; i < byteString.length; ++i) {
ui[i] = byteString.charCodeAt(i)
}
return ui
}
async function verifyAndFetch(request) {
const url = new URL(request.url)
// If the path does not begin with our protected prefix, just pass the request
// through.
if (!url.pathname.startsWith("/verify/")) {
return fetch(request)
}
// Make sure we have the minimum necessary query parameters.
if (!url.searchParams.has("mac") || !url.searchParams.has("expiry")) {
return new Response("Missing query parameter", { status: 403 })
}
const key = await crypto.subtle.importKey(
"raw",
secretKeyData,
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"],
)
// Extract the query parameters we need and run the HMAC algorithm on the
// parts of the request we are authenticating: the path and the expiration
// timestamp.
const expiry = Number(url.searchParams.get("expiry"))
const dataToAuthenticate = url.pathname + expiry
// The received MAC is Base64-encoded, so we have to go to some trouble to
// get it into a buffer type that crypto.subtle.verify() can read.
const receivedMacBase64 = url.searchParams.get("mac")
const receivedMac = byteStringToUint8Array(atob(receivedMacBase64))
// Use crypto.subtle.verify() to guard against timing attacks. Since HMACs use
// symmetric keys, we could implement this by calling crypto.subtle.sign() and
// then doing a string comparison -- this is insecure, as string comparisons
// bail out on the first mismatch, which leaks information to potential
// attackers.
const verified = await crypto.subtle.verify(
"HMAC",
key,
receivedMac,
encoder.encode(dataToAuthenticate),
)
if (!verified) {
const body = "Invalid MAC"
return new Response(body, { status: 403 })
}
if (Date.now() > expiry) {
const body = `URL expired at ${new Date(expiry)}`
return new Response(body, { status: 403 })
}
// We have verified the MAC and expiration time; we are good to pass the request
// through.
return fetch(request)
}
addEventListener("fetch", event => {
event.respondWith(verifyAndFetch(event.request))
})