You may only deploy this edge function using CDN-as-Code due to Node.js dependencies. It will not work when deployed through the Edgio Console.
Request signing is a technique used to verify the authenticity and integrity of a request. It involves adding a signature to the request, which is generated using a secret key and specific request parameters. This signature can then be verified by the recipient to ensure that the request has not been tampered with and originated from a trusted source.
Request signing can be used in various scenarios, such as API authentication, secure communication between services, and preventing replay attacks. By including a signature with each request, both the sender and receiver can have confidence in the integrity and authenticity of the data being exchanged.
Router Configuration
In the Edgio router, you can use the
edge_function
feature to specify the path to the edge function that will handle the request signing and verification. Because this edge function is designed to handle both signing and verification, we’ll match any request beginning with /sign/
or /verify/
, and capture the remaining path for use in the edge function.JavaScriptroutes.js
1import {Router, edgioRoutes} from '@edgio/core';23export default new Router()4 .use(edgioRoutes)56 .match(/\/(sign|verify)\/(.*)/, {7 edge_function: './edge-functions/main.js',8 });
Edge Function
The edge function will be responsible for generating a signed URL for the given request, or verifying the signature of a request and forwarding it to the origin. The edge function will be invoked for any request that matches the route above, so we’ll need to check the request path to determine whether we are signing or verifying the request.
In either case, we’ll need to generate a signature using a cryptographic hash function. In this example, we’ll use the HMAC-SHA1 algorithm, which is a widely used cryptographic hash function. The signature will be generated using a secret key, which should be defined as an environment variable in the Edgio Console. The secret key should never be shared publicly, and should be kept private to ensure that the signature cannot be forged.
The Edge Function runtime does not currently support a native crypto library, so a third-party library to generate the signature is needed. In this example, we’ll use the crypto-js library.
JavaScriptedge-functions/main.js
1import HmacSHA1 from 'crypto-js/hmac-sha1';2import Base64 from 'crypto-js/enc-base64';34export async function handleHttpRequest(request, context) {5 // ** IMPORTANT **6 // Secret key should be defined as an environment variable in the Edgio console7 const secretKey = '$0m3th!ngS3cr3t'; // context.environmentVars.REQ_SIGNING_SECRET_KEY;89 if (request.url.includes('/sign/')) {10 return generateSignedUrl(request, secretKey);11 }1213 return verifyAndFetch(request, secretKey);14}1516/**17 * Generates a signed URL for the given URL and secret key18 * @param {URL} url19 * @param {string} key20 */21async function generateSignedUrl(request, key) {22 const url = new URL(request.url);2324 // Replace /sign/ with /verify/ in the URL since we are generating a signed URL for verification25 url.pathname = url.pathname.replace('/sign/', '/verify/');2627 const expirationMs = 1000 * 60 * 5; // 5 minutes28 const expiry = Date.now() + expirationMs;29 const dataToAuthenticate = url.pathname + expiry;3031 const hash = HmacSHA1(dataToAuthenticate, key);32 const base64Mac = Base64.stringify(hash);3334 url.searchParams.set('mac', base64Mac);35 url.searchParams.set('expiry', expiry.toString());3637 // respond with the signed URL that can be used to verify the request38 return new Response(url.toString());39}4041/**42 * Verifies the MAC and expiry of the given URL. If the URL is valid, the request is forwarded to the origin.43 */44async function verifyAndFetch(request, key) {45 const invalidResponse = (reason) =>46 new Response(`Invalid request - ${reason}`, {status: 403});47 const url = new URL(request.url);4849 if (!url.searchParams.has('mac') || !url.searchParams.has('expiry')) {50 return invalidResponse('Missing MAC or expiry');51 }5253 const expiry = Number(url.searchParams.get('expiry'));54 const dataToAuthenticate = url.pathname + expiry;5556 const receivedMacBase64 = url.searchParams.get('mac');57 const receivedMac = Base64.parse(receivedMacBase64);5859 const hash = HmacSHA1(dataToAuthenticate, key);60 const hashInBase64 = Base64.stringify(hash);6162 // Ensure that the MAC is valid63 if (hashInBase64 !== receivedMacBase64) {64 return invalidResponse('Invalid MAC');65 }6667 // Ensure that the URL has not expired68 if (Date.now() > expiry) {69 return invalidResponse('URL has expired');70 }7172 // Forward the remaining request path after **/verify/* to the origin73 url.pathname = url.pathname.split('/verify/')[1];7475 return fetch(url.toString(), {76 edgio: {origin: 'web'},77 });78}