CacheFly Signed URLs (2.0.1)

Download OpenAPI specification:Download

This document describes the configuration format for CacheFly Signed URLs.

Introduction

Signed URLs provide a way to limit access to premium or private content. By adding a signature into the URL we can enforce your dynamically configured policies, such as expiry and location.

Several different algorithms are supported, with additional algorithms being added on request. Our intent is for our CDN to be able to accomidate the way that you want to sign URLs.

Once Signed URLs are enabled for your account, you will have the option of creating a configuration file. This configuration is then associated with your services. This gives you full control over when signing is enforced.

You may upload the configuration file to the CDN via the API or the Portal.

Please note that this configurable Signed URLs service is a more advance alternative to our own ProtectServe service. If you need simplicy then ProtectServe may be a better choice.

Format

The configuration file for Signed URLs may be provided to us in either YAML or JSON format. We have supported both as a convenience to our customers. We are aware that some customer automations find it easier to work with JSON. Please pay attention to the MIME type while uploading the configuration.

All the configuration examples here are shown in YAML format. YAML is intended to be more human friendly than JSON. It is reasonably easy to follow (without any experience), but sometimes writing it takes a little getting used to. The main advantages of YAML are the lack of curly braces, the use of indentation to define the structure, and the ability to add comments.

If you are not familiar with YAML, there are many great resources online to help you get started.

Additionally a YAML aware IDE or editor will help you with authoring and modifying the configuration file.

The editor within our Portal is based on the well known Visual Studio Code editor, and will show various warnings and errors as you type. You should ensure that all of these are addressed before saving the configuration.

However our Portal is intended to be useful and convenient (we're a CDN, not a fully featured IDE company). As such we also wish to provide you with a list of other tools that our team has found useful;

If you require any support with authoring or modifying your configuration files, please contact our support team who will be very happy to assist.

Deployment

After uploading your configuration to us, our systems will deploy it to the CDN (almost) immediately.

When applying changes there may, or may not, be a propagation delay. This is to ensure that a high frequency of changes by a small number of users does not negatively impact the performance of the network as a whole. As such, although we endeavour to make this as fast as possible, and immediate in most cases, the only guarantee is that it will occur eventually.

Some limited validation checks are performed during upload. If they fail your new configuration not be applied and the existing configuration will continue to be used. However there are many small details that can not be checked automatically, and it is easy to break an in production system by introducing an error into the configuration.

We recommend that you:

  • Keep backups of your configuration files
  • Consider testing the configuration using a CacheFly service which is not serving your production traffic
  • Ensure that you are fully confident in the configuration before you upload it

It is possible for us to revert to a previous configuration if you have a problem. However this is a manual process and will take some time to complete.

Configuration

The Signed URLs configuration file contains a single key called algorithms, inside of which you list all the different signing algorithm configurations that you wish to use for your service.

The value of the name property informs us of which algorithm to use.

The value of the path property tells us which paths you want to protect with this configuration.

Example

YAML

---
algorithms:

  # This is the first configuration for this service
  - name: "CLOUDFLARE"            # name of the algorithm
    path: "/data"                 # the path being protected
    secret: "19GTkGGYKYgL7ZvI"    # the secret used for the signatures

  # This is the second configuration for this service
  - name: "CLOUDFLARE"
    path: "/video"
    secret: "BC423lkds382X3cc"

JSON

{
  "algorithms": [
    {
      "name": "CLOUDFLARE",
      "path": "/data",
      "secret": "19GTkGGYKYgL7ZvI"
    },
    {
      "name": "CLOUDFLARE",
      "path": "/video",
      "secret": "BC423lkds382X3cc"
    }
  ]
}

Processing

The CDN attempts to match incoming requests to one of the configurations in the list based on the path.

When the signature is found to be invalid or expired, the request is denied and a 403 is returned.

If there is no match, then the request is allowed by default.

If there are multiple matches, only the first is considered.

Supported Algorithms

If you require an algorithm other than those described here please contact our support team. Note the easiest way to do this is to use the Open a ticket option in our customer portal.

Cloudflare

This algorithm matches the default Signed URLs algorithm used by Cloudflare.

Required fields

  • name

    You must specify the name as CLOUDFLARE.

  • path

    You must specify a path. Use may the value / to mean all paths.

  • secret

    You must specify the secret used which is during the signing.

Optional fields

  • queryParamTokenName

    This allows you to specify the name of the query parameter which stores the signature.

    This defaults to the value mac.

  • queryParamExpiryName

    The name of the query parameter which contains the expiry date.

    This defaults to the value expiry.

Example

---
algorithms:

  # Example Cloudflare style Signed URL
  #
  # https://example.cachefly.net/data/file/video.mp4?mac=29QpicPWKD6RpuYMfC8LfA==&expiry=1389183132
  #
  - name: "CLOUDFLARE"                # name of the algorithm
    path: "/data"                     # the path being protected
    secret: "19GTkGGYKYgL7ZvI"        # the secret used for the signatures
    queryParamTokenName: "mac"        # optional: query parameter name containing the signature
    queryParamExpiryName: "expiry"    # optional: query parameter name containing an expiry timestamp

Generating the signature

The following example shows how to generate the signature using Python 3:

import hashlib
import hmac
import base64
from urllib.parse import urlparse, urlunparse, urlencode

def generate_signed_url(url, secret_key, expiry):
  # Create the data to be authenticated
  data_to_authenticate = urlparse(url).path + "@" + str(expiry)

  # Calculate the HMAC hash for the data
  mac = hmac.new(secret_key.encode(), data_to_authenticate.encode(), hashlib.sha256).digest()
  encoded_mac = base64.b64encode(mac).decode()

  # Append the query parameters to the URL
  parsed_url = urlparse(url)
  query_params = {'mac': encoded_mac, 'expiry': expiry}
  updated_query = urlencode(query_params)
  signed_url = urlunparse((parsed_url.scheme, parsed_url.netloc, parsed_url.path, parsed_url.params, updated_query, parsed_url.fragment))

  return signed_url

# Example usage:
original_url = "https://example.com/verify/path/to/resource"
secret_key = "my secret symmetric key"
expiry = int(time()) + 3600  # Set the URL expiration to one hour from now

signed_url = generate_signed_url(original_url, secret_key, expiry)

print("Signed URL:", signed_url)

The following example shows how to generate the signature using PHP:

function generateSignedUrl(string $url, string $secretKey, int $expiry): string
  {
    // Create the data to be authenticated
    $dataToAuthenticate = parse_url($url, PHP_URL_PATH) . "@" . $expiry;

    // Calculate the HMAC hash for the data
    $mac = hash_hmac("sha256", $dataToAuthenticate, $secretKey, true);
    $encodedMac = base64_encode($mac);

    // Append the query parameters to the URL
    $parsedUrl = parse_url($url);
    $parsedUrl['query'] = http_build_query(['mac' => $encodedMac, 'expiry' => $expiry]);

    // Rebuild the URL with the updated query parameters
    $signedUrl = $parsedUrl['scheme'] . '://' . $parsedUrl['host'] . $parsedUrl['path'] . '?' . $parsedUrl['query'];

    return $signedUrl;
  }

// Example usage:
$originalUrl = "https://example.com/verify/path/to/resource";
$secretKey = "my secret symmetric key";
$expiry = time() + 3600; // Set the URL expiration to one hour from now

$signedUrl = generateSignedUrl($originalUrl, $secretKey, $expiry);

echo "Signed URL: " . $signedUrl;

CDN77

This algorithm matches the CDN77 secure token functionality by default. Additionally options are provided for being able to pass signature and expiry date via cookies.

Required fields

  • name

    You must specify the name as CDN77.

  • path

    You must specify a path. Use may the value / to mean all paths.

  • type

    You must specify a type, which informs us where to find the signature.

    The supported types are:

    type description
    QUERY Signature is in query parameter(s)
    PATH Signature is in the path
    COOKIE Signature is in a cookie
  • secret

    You must specify the secret used which is during the signing.

Optional fields

  • queryParamName

    Only used when the type property is set to QUERY.

    This allows you to specify the name of the query parameter which stores the signature.

    This defaults to the value secure.

  • cookieTokenField

    Only used when the type property is set to COOKIE.

    This allows you to specify the name of the cookie which stores the signature.

    Note that cookie names are always case sensitive.

    This defaults to the value token.

  • cookieExpiryField

    Only used when the type property is set to COOKIE.

    This allows you to specify the name of the cookie which stores an expiry date.

    Note that cookie names are always case sensitive.

    This defaults to the value expiry.

Example

---
algorithms:

  # Example CDN77 style secure token using query parameter
  #
  # https://example.cachefly.net/private/video.mp4?secure=29QpicPWKD6RpuYMfC8LfA==,1389183132
  #
  - name: "CDN77"                 # name of the algorithm
    path: "/private"              # the path being protected
    type: "QUERY"                 # the implementation type
    secret: "19GTkGGYKYgL7ZvI"    # the secret used for the signatures
    queryParamName: "secure"      # optional: query parameter name containing the signature

  # Example CDN77 style secure token using path
  #
  # https://example.cachefly.net/z--FA_CsNsR2TOV2eg9q4w==,1389183132/downloads/video.mp4
  #
  - name: "CDN77"                 # name of the algorithm
    path: "/downloads"            # the path being protected
    type: "PATH"                  # the implementation type
    secret: "19GTkGGYKYgL7ZvI"    # the secret used for the signatures

  # Example CDN77 style secure token using cookies
  #
  # https://example.cachefly.net/video/playlist/d.m3u8
  #
  # COOKIES:
  #  - token=29QpicPWKD6RpuYMfC8LfA==
  #  - expiry=1389183132
  #
  - name: "CDN77"                 # name of the algorithm
    path: "/video"                # the path being protected
    type: "COOKIE"                # the implementation type
    secret: "19GTkGGYKYgL7ZvI"    # the secret used for the signatures
    cookieTokenField: "token"     # optional: cookie name containing the signature
    cookieExpiryField: "expiry"   # optional: cookie name containing an expiry timestamp

Generating the signature

The following code examples show how to generate the signature using PHP.

Query Parameter

function getSignedUrlQueryParameter(
  string $cdnResourceUrl,
  string $filePath,
  string $secureToken,
  ?int $expiryTimestamp = null
): string {
  $filePath = '/' . ltrim($filePath, '/'); // Add slash to start of file path if missing

  if ($positionOfStartQuery = strpos($filePath, '?')) {
    $filePath = substr($filePath, 0, $positionOfStartQuery); // Cut the query string from file path
  }

  $hash = $filePath . $secureToken;

  if ($expiryTimestamp) {
    $hash = $expiryTimestamp . $hash;
  }

  $finalHash = strtr(base64_encode(md5($hash, true)), '+/', '-_'); // Replace invalid URL query string characters

  return "https://{$cdnResourceUrl}{$filePath}?secure={$finalHash}" . ($expiryTimestamp ? ",{$expiryTimestamp}" : '');
}

Path

function getSignedUrlPath(
  string $cdnResourceUrl,
  string $filePath,
  string $secureToken,
  ?int $expiryTimestamp = null
): string {
  $strippedPath = substr($filePath, 0, strrpos($filePath, '/'));

  $strippedPath = '/' . ltrim($strippedPath, '/');
  $filePath = '/' . ltrim($filePath, '/');

  if ($positionOfStartQuery = strpos($strippedPath, '?')) {
    $filePath = substr($strippedPath, 0, $positionOfStartQuery);
  }

  $hash = $strippedPath . $secureToken;

  if ($expiryTimestamp) {
    $hash = $expiryTimestamp . $hash;
  }

  $finalHash = strtr(base64_encode(md5($hash, true)), '+/', '-_');

  return "https://{$cdnResourceUrl}/{$finalHash}" . ($expiryTimestamp ? ",{$expiryTimestamp}" : '') . $filePath;
}