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

Sign requests

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

// We'll need some super-secret data to use as a symmetric key.
const encoder = new TextEncoder();
const secretKeyData = encoder.encode('my secret symmetric key');
async function generateSignedUrl(url) {
const key = await crypto.subtle.importKey(
'raw',
secretKeyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
// Signed requests expire after one minute. Note that you could choose
// expiration durations dynamically, depending on, e.g. the path or a query
// parameter.
const expirationMs = 60000;
const expiry = Date.now() + expirationMs;
const dataToAuthenticate = url.pathname + expiry;
const mac = await crypto.subtle.sign(
'HMAC',
key,
encoder.encode(dataToAuthenticate)
);
// `mac` is an ArrayBuffer, so we need to jump through a couple of hoops to get
// it into a ByteString, and then a Base64-encoded string.
const base64Mac = btoa(String.fromCharCode(...new Uint8Array(mac)));
url.searchParams.set('mac', base64Mac);
url.searchParams.set('expiry', String(expiry));
return new Response(url.href);
}
export default {
fetch(req) {
const prefix = '/sign/';
const url = new URL(req.url);
if (url.pathname.startsWith(prefix)) {
// Rewrite the URL to be sent to the "verifying-requests" example domain.
url.pathname = `/verify/${url.pathname.slice(prefix.length)}`;
url.hostname = 'verifying-requests.workers.dev';
return generateSignedUrl(url);
}
return fetch(req);
},
};

Generating signed requests

Typically, signed requests are delivered to the user in some out-of-band way, such as email or are generated by the user themselves if they possess the symmetric key. You can also generate signed requests from within a Workers app.

For request URLs beginning with /generate/, we replace /generate/ with /verify/, sign the resulting path with its timestamp, and return the full, signed URL in the response body.

async function generateSignedUrl(url) {
// We"ll need some super-secret data to use as a symmetric key.
const encoder = new TextEncoder()
const secretKeyData = encoder.encode("my secret symmetric key")
const key = await crypto.subtle.importKey(
"raw",
secretKeyData,
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
)
// Signed requests expire after one minute. Note that you could choose
// expiration durations dynamically, depending on, e.g. the path or a query
// parameter.
const expirationMs = 60000
const expiry = Date.now() + expirationMs
const dataToAuthenticate = url.pathname + expiry
const mac = await crypto.subtle.sign("HMAC", key, encoder.encode(dataToAuthenticate))
// `mac` is an ArrayBuffer, so we need to jump through a couple of hoops to get
// it into a ByteString, and then a Base64-encoded string.
const base64Mac = btoa(String.fromCharCode(...new Uint8Array(mac)))
url.searchParams.set("mac", base64Mac)
url.searchParams.set("expiry", expiry)
return new Response(url)
}
addEventListener("fetch", event => {
const url = new URL(event.request.url)
const prefix = "/generate/"
if (url.pathname.startsWith(prefix)) {
// Replace the "/generate/" path prefix with "/verify/", which we
// use in the first example to recognize authenticated paths.
url.pathname = `/verify/${url.pathname.slice(prefix.length)}`
event.respondWith(generateSignedUrl(url))
} else {
event.respondWith(fetch(event.request))
}
})