Skip to main content

Secure Token for Video CDN

The player requests the master manifest, then requests each nested file on its own. Query string forwarding copies md5 and expires from that first request onto the files that follow, so HLS and MPEG-DASH need Secure Token and Query String Forwarding together.

Secure Token

Enable Secure Token in the CDN resource settings. A protected URL looks like this:

http://demo-files-protected.gvideo.io/coffee_run/master.m3u8?md5=eBx15p01_a9JNuo1iZpTfQ&expires=1893456000&other=parameter

When the token is valid, CDN returns video manifests and segments with 200 OK. When the token expires, CDN returns 403 Forbidden or 410 Gone, so the expired URL no longer downloads the file.

Token refresh replaces a short-lived token in the player before playback stops.

Expired secure token returns 410 for a video segment

Query String Forwarding

HLS and MPEG-DASH split playback across a master manifest and the rendition manifests, segments, and subtitles linked from it. The player requests the master manifest first, then follows those links.

For protected HLS or MPEG-DASH video, enable Query String Forwarding so CDN copies the token parameters from the master manifest request onto the related files.

For HLS and MPEG-DASH video protected by Secure Token, configure Query String Forwarding with these values:

FieldValues
Forward from file typesm3u8, mpd
Forward to file typesm3u8, ts, mp4, m4s, vtt
Forward only keysmd5, expires

Use m3u8 for HLS manifests and mpd for MPEG-DASH manifests. Use ts, mp4, m4s, and vtt for nested media and subtitle files. Add m4a or m4v when the manifests reference those files and they also need the same query parameters.

With Query String Forwarding enabled, a request to:

/coffee_run/master.m3u8?md5=TOKEN&expires=EXPIRY

is propagated to all nested manifests and segments inside the manifest body automatically.

Query string parameters are inserted into manifest body links automatically

As a result, the player reads tokens from the modified manifests and does not attach a separate token to each file.

Query string parameters are forwarded from the master manifest to nested manifests and segments

Token path rule

For requests with Secure Token and Query String Forwarding enabled, generate the token for the directory path, not for the exact manifest filename. One token then covers every file in that directory.

Use this directory path:

/coffee_run/

Do not sign each file on its own:

/coffee_run/master.m3u8
/coffee_run/index-svod720n-v1-a1.m3u8
/coffee_run/segment-1-svod720n-v1-a1.ts
/coffee_run/master.mpd
/coffee_run/dash-init-f1-v1-x3.m4s
/coffee_run/dash-segment-1-f1-v1-x3.m4s
...

The token covers every file in /coffee_run/, while a file in a subdirectory needs its own token.

URL matching, IP binding, and 403 errors from mobile IP rotation (CGNAT) are handled in Secure Token. Nested files receive md5 and expires through query string forwarding.

Use these links to check how Secure Token and Query String Forwarding work with a real HLS or MPEG-DASH stream:

The demo files stay valid until January 2030.

Protected MPEG-DASH stream opened in dash.js player

A secure token checks a temporary signature on a video URL. Query string forwarding copies md5 and expires from the master manifest onto the files the player requests next. HLS and MPEG-DASH need both options on.

An API token is required. The CDN resource ID is the id from the resources list call.

export GCORE_API_KEY="{YOUR_API_KEY}"
export CDN_RESOURCE_ID="{YOUR_CDN_RESOURCE_ID}"

Secure token and forwarding

This call turns on secure token and query string forwarding together.

secure_key requires enabled and key. type 2 leaves the client IP out of the signature. A request that omits key returns HTTP 400.

query_string_forwarding reads md5 and expires from m3u8 and mpd files and adds them to m3u8, ts, mp4, m4s, and vtt files. enabled, forward_from_file_types, and forward_to_file_types are required.

import os

from gcore import CDB

client = CDB()
resource_id = int(os.environ["CDN_RESOURCE_ID"])

updated = client.cdn.cdn_resources.update(
    resource_id,
    options={
        "secure_key": {"enabled": True, "key": "video-secure-key", "type": 2},
        "query_string_forwarding": {
            "enabled": True,
            "forward_from_file_types": ["m3u8", "mpd"],
            "forward_to_file_types": ["m3u8", "ts", "mp4", "m4s", "vtt"],
            "forward_only_keys": ["md5", "expires"],
        },
    },
)
secure = updated.options.secure_key
forwarding = updated.options.query_string_forwarding
print(secure.enabled, secure.key, secure.type)
print(
    forwarding.enabled,
    forwarding.forward_from_file_types,
    forwarding.forward_to_file_types,
    forwarding.forward_only_keys,
)
package main

import (
    "context"
    "fmt"
    "os"
    "strconv"

    gcore "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/cdn"
)

func main() {
    client := gcore.NewClient()
    ctx := context.Background()
    resourceID, err := strconv.ParseInt(os.Getenv("CDN_RESOURCE_ID"), 10, 64)
    if err != nil {
        panic(err)
    }

    updated, err := client.CDN.CDNResources.Update(ctx, resourceID, cdn.CDNResourceUpdateParams{
        Options: cdn.CDNResourceUpdateParamsOptions{
            SecureKey: cdn.CDNResourceUpdateParamsOptionsSecureKey{
                Enabled: true,
                Key:     gcore.String("video-secure-key"),
                Type:    2,
            },
            QueryStringForwarding: cdn.CDNResourceUpdateParamsOptionsQueryStringForwarding{
                Enabled:              true,
                ForwardFromFileTypes: []string{"m3u8", "mpd"},
                ForwardToFileTypes:   []string{"m3u8", "ts", "mp4", "m4s", "vtt"},
                ForwardOnlyKeys:      []string{"md5", "expires"},
            },
        },
    })
    if err != nil {
        panic(err)
    }
    secure := updated.Options.SecureKey
    forwarding := updated.Options.QueryStringForwarding
    fmt.Println(secure.Enabled, secure.Key, secure.Type)
    fmt.Println(forwarding.Enabled, forwarding.ForwardFromFileTypes, forwarding.ForwardToFileTypes, forwarding.ForwardOnlyKeys)
}
curl -X PATCH "https://api.cdb-staging.cdn.orange.com/cdn/resources/$CDN_RESOURCE_ID" \
  -H "Authorization: APIKey $GCORE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "options": {
      "secure_key": {
        "enabled": true,
        "key": "video-secure-key",
        "type": 2
      },
      "query_string_forwarding": {
        "enabled": true,
        "forward_from_file_types": ["m3u8", "mpd"],
        "forward_to_file_types": ["m3u8", "ts", "mp4", "m4s", "vtt"],
        "forward_only_keys": ["md5", "expires"]
      }
    }
  }'

The API returns HTTP 200. The saved options are:

{
  "secure_key": {
    "enabled": true,
    "key": "video-secure-key",
    "type": 2
  },
  "query_string_forwarding": {
    "enabled": true,
    "forward_from_file_types": ["m3u8", "mpd"],
    "forward_to_file_types": ["m3u8", "ts", "mp4", "m4s", "vtt"],
    "forward_only_keys": ["md5", "expires"]
  }
}

type 0 includes the client IP. Sending only secure_key leaves query string forwarding in place.

Sign the directory path /coffee_run/, not each file name inside it. The token does not cover subdirectories.