Sign requests
Verify a signed request using the HMAC and SHA-256 algorithms or return a 403.
export default {  async fetch(request) {    // You 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;    }
    const url = new URL(request.url);
    // If the path does not begin with our protected prefix, pass the request through    if (!url.pathname.startsWith("/verify/")) {      return fetch(request);    }
    // Make sure you 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. It is crucial to pad the input data, for example, by adding a symbol    // in-between the two fields that can never occur on the right side. In this    // case, use the @ symbol to separate the fields.    const expiry = Number(url.searchParams.get("expiry"));    const dataToAuthenticate = `${url.pathname}@${expiry}`;
    // The received MAC is Base64-encoded, so you 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, you 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 });    }
    // you have verified the MAC and expiration time; you can now pass the request    // through.    return fetch(request);  },
};
const handler: ExportedHandler = {  async fetch(request) {    // You 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;    }
    const url = new URL(request.url);
    // If the path does not begin with our protected prefix, pass the request through    if (!url.pathname.startsWith("/verify/")) {      return fetch(request);    }
    // Make sure you 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. It is crucial to pad the input data, for example, by adding a symbol    // in-between the two fields that can never occur on the right side. In this    // case, use the @ symbol to separate the fields.    const expiry = Number(url.searchParams.get("expiry"));    const dataToAuthenticate = `${url.pathname}@${expiry}`;
    // The received MAC is Base64-encoded, so you 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, you 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 });    }
    // you have verified the MAC and expiration time; you can now pass the request    // through.    return fetch(request);  },
};
export default handler;
 Generating signed requests
You can generate signed requests from within a Worker using the Web Crypto APIs.
For request URLs beginning with /generate/, replace /generate/ with /verify/, sign the resulting path with its timestamp, and return the full, signed URL in the response body.
export default {  async fetch(request) {    async function generateSignedUrl(url) {      // You will 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, for example, the path or a query      // parameter.      const expirationMs = 60000;      const expiry = Date.now() + expirationMs;      // The signature will be computed for the pathname and the expiry timestamp.      // The two fields must be separated or padded to ensure that an attacker      // will not be able to use the same signature for other pathname/expiry pairs.      // The @ symbol is guaranteed not to appear in expiry, which is a (decimal)      // number, so you can safely use it as a separator here. When combining more      // fields, consider JSON.stringify-ing an array of the fields instead of      // concatenating the values.      const dataToAuthenticate = `${url.pathname}@${expiry}`;
      const mac = await crypto.subtle.sign(        "HMAC",        key,        encoder.encode(dataToAuthenticate)      );
      // `mac` is an ArrayBuffer, so you need to make a few changes to get      // it into a ByteString, and then a Base64-encoded string.      let base64Mac = btoa(String.fromCharCode(...new Uint8Array(mac)));
      // must convert "+" to "-" as urls encode "+" as " "      base64Mac = base64Mac.replaceAll("+", "-");      url.searchParams.set("mac", base64Mac);      url.searchParams.set("expiry", expiry);
      return new Response(url);    }
    const url = new URL(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)}`;      return await generateSignedUrl(url);    } else {      return fetch(request);    }  },
};
const handler: ExportedHandler = {  async fetch(request: Request) {    async function generateSignedUrl(url) {      // You will 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, for example, the path or a query      // parameter.      const expirationMs = 60000;      const expiry = Date.now() + expirationMs;      // The signature will be computed for the pathname and the expiry timestamp.      // The two fields must be separated or padded to ensure that an attacker      // will not be able to use the same signature for other pathname/expiry pairs.      // The @ symbol is guaranteed not to appear in expiry, which is a (decimal)      // number, so you can safely use it as a separator here. When combining more      // fields, consider JSON.stringify-ing an array of the fields instead of      // concatenating the values.      const dataToAuthenticate = `${url.pathname}@${expiry}`;
      const mac = await crypto.subtle.sign(        "HMAC",        key,        encoder.encode(dataToAuthenticate)      );
      // `mac` is an ArrayBuffer, so you need to make a few changes to get      // it into a ByteString, and then a Base64-encoded string.      let base64Mac = btoa(String.fromCharCode(...new Uint8Array(mac)));
      // must convert "+" to "-" as urls encode "+" as " "      base64Mac = base64Mac.replaceAll("+", "-");      url.searchParams.set("mac", base64Mac);      url.searchParams.set("expiry", expiry);
      return new Response(url);    }
    const url = new URL(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)}`;      return await generateSignedUrl(url);    } else {      return fetch(request);    }  },
};
export default handler;