CDNetworks Documentation Edge Application Developer Tools Request Signature and Legitimacy Verification

Request Signature and Legitimacy Verification

Last update:2024-09-03 17:00:38

This example demonstrates how to secure access to specific website resources using Edge Cloud Apps Function and the Web Crypto API. It utilizes a pet shop’s “About Us” page located at http://ecatest.cdnetworks.com/ECA-test/pet-shop-website-template/about.html as a practical example. Accessing this page requires a valid signature generated using the HMAC and SHA-256 algorithms. The Edge Cloud Apps Function can either sign outgoing requests on behalf of the client or verify incoming signed requests, acting as a security gatekeeper. Unauthorized requests will be met with an error page.

Benefits

  • Enhanced Security: Employing cryptographic signatures effectively prevents unauthorized access, safeguarding sensitive website resources.
  • Edge Cryptography: Leveraging Edge Cloud Apps Functions and the Web Crypto API enables cryptographic operations directly at the edge, reducing latency and enhancing efficiency.
  • Flexible Control: Developers can customize signing algorithms and verification logic to implement flexible access control strategies based on specific requirements.

Code Example

const SECRET_KEY = "my secret symmetric key";

// Handles requests, either signing or verifying based on the request method
async function handleRequest(request) {
    if (request.method === "POST") {
        return signRequest(request);
    } else {
        return verifyRequest(request);
    } 
}

// Verifies the request signature
async function verifyRequest(request) {
    const url = new URL(request.url);

    // Ensure the request contains the necessary query parameters
    if (!url.searchParams.has("mac") || !url.searchParams.has("expiry")) {
        return failPage("Missing query parameters");
    }

    const encoder = new TextEncoder();
    const secretKeyData = encoder.encode(SECRET_KEY);

    // Import the key for signature verification
    const key = await crypto.subtle.importKey(
        "raw",
        secretKeyData,
        { name: "HMAC", hash: "SHA-256" },
        false,
        ["verify"],
    );

    // Extract data requiring verification: path and expiry timestamp
    const expiry = Number(url.searchParams.get("expiry"));
    const dataToAuthenticate = url.pathname + expiry;

    // Convert the received Base64-encoded MAC to a Uint8Array
    const receivedMacBase64 = url.searchParams.get("mac");
    const receivedMac = byteStringToUint8Array(atob(receivedMacBase64));

    // Utilize crypto.subtle.verify() for signature verification to prevent timing attacks
    const verified = await crypto.subtle.verify(
        "HMAC",
        key,
        receivedMac,
        encoder.encode(dataToAuthenticate),
    );

    // If verification fails, return an error page
    if (!verified) {
        return failPage("Invalid MAC");
    }

    // Check if the URL has expired
    if (Date.now() > expiry) {
        return failPage(`URL expired at ${new Date(expiry)}`);
    }

    // Verification successful, allow access to the resource
    return fetch(request);
}

// Convert a ByteString to a Uint8Array
function byteStringToUint8Array(byteString) {
    const ui = new Uint8Array(byteString.length);
    for (let i = 0; i < byteString.length; ++i) {
        ui[i] = byteString.charCodeAt(i);
    }
    return ui;
}

// Signs the request
async function signRequest(request) {
    const url = new URL(request.url);
    return generateSignedUrl(url);
}

// Generates a signed URL
async function generateSignedUrl(url) {
    // Import the key for signature generation
    const encoder = new TextEncoder();
    const secretKeyData = encoder.encode(SECRET_KEY);
    const key = await crypto.subtle.importKey(
      "raw",
      secretKeyData,
      { name: "HMAC", hash: "SHA-256" },
      false,
      ["sign"],
    );
  
    // Set URL expiration time
    const expirationMs = 18000; // 18 seconds
    const expiry = Date.now() + expirationMs;
    const dataToAuthenticate = url.pathname + expiry;
  
    // Generate signature
    const mac = await crypto.subtle.sign("HMAC", key, encoder.encode(dataToAuthenticate));
  
    // Convert the signature to a Base64-encoded string
    const base64Mac = btoa(String.fromCharCode(...new Uint8Array(mac)));
  
    // Add the signature and expiration time to the URL query parameters
    url.searchParams.set("mac", base64Mac);
    url.searchParams.set("expiry", expiry);
  
    return new Response(url);
  }

// Generate error page
function failPage(msg) {
    let page = FAILPAGE.replace('{{error-msg}}', msg);
    return new Response(page, {headers: {"Content-Type": "text/html; charset=utf-8"}});
}

// Error page HTML template
const FAILPAGE = `
<!DOCTYPE html>
<html>
<head>
<title>Error Page</title>
<style>
// ... Page styles ... 
</style>
</head>
<body>
<div class="container">
<h1>Error</h1>
<p>The URL you are trying to access is not correctly signed: {{error-msg}}</p>
<div class="url-section">
    <div class="url-wrapper">
        <p><span class="url-display" id="current-url" style="display: block;"></span></p>
    </div>
    <div class="button-wrapper">
        <button class="url-button" type="button" onclick="getSignedUrl()">Sign URL</button>
    </div>
    <div class="url-wrapper" id="signed-url-section">
        <p><strong>Signed URL will expire in 18 seconds</strong></p>
        <p><span class="url-display" id="signed-url"></span></p>
    </div>
</div>
</div>

<script>
// ... Page script ... 
</script>
</body>
</html>
`;
                    
addEventListener("fetch", event => {
    return event.respondWith(handleRequest(event.request));
});

Note

Edge Cloud Apps Function fully support the Web Crypto API, enabling you to meet your encryption and decryption needs at the edge. You can use Edge Cloud Apps Functions to sign requests on behalf of the client or to validate incoming signed requests, effectively preventing unauthorized access (hotlinking protection).

Is the content of this document helpful to you?
Yes
I have suggestion
Submitted successfully! Thank you very much for your feedback, we will continue to strive to do better!