Skip to main content

Signed URL tokens

Signed URL tokens protect a domain's paths so only requests carrying a valid, unexpired, correctly-signed token are served from the edge. You enable the layer and create the signing Secret key in the console — see Domain configuration → Access Control — then your application generates a token for each request using that secret.

The edge extracts the token from the request, recomputes the HMAC with the stored secret and validates the time window (and, where used, the IP and path). On success the edge strips the token fields and fetches from origin; on failure it returns HTTP 403.

Redirection runs first

Token validation does not run if the path also has a Redirection rule — the redirect is executed before access control. Keep protected paths free of redirects.

Which scheme to use​

The Algorithm you pick on a token rule selects the scheme:

Console algorithmSchemeHashTypical use
HMAC-SHA256Canal tokenHMAC-SHA256Current default — session/ACL tokens for VOD & live
HMAC-MD5 (deprecated)Token Schema V2MD5Standard CDN secure-link scripts
—Basic tokenHMAC-SHA1Legacy query-string signing

All timestamps are UTC. Generate them server-side (date -u …) and never ship the secret key to the client — sign each request on your backend.

Basic token​

Query-string signing with a time window. Required fields:

  • stime — start (valid-from), yyyymmddHHMMSS UTC (e.g. 20240824115300).
  • etime — end (expiry), same format.
  • URI — the resource path (method and hostname removed).
  • ip — optional client IP, e.g. &ip=1.2.3.4.

Steps for http://www.sample.com/path/to/resource?clientId=12345&product=A123:

  1. Strip method + hostname → /path/to/resource?clientId=12345&product=A123.
  2. Append the mandatory time fields: /path/to/resource?clientId=12345&product=A123&stime=20081201060100&etime=20081201183000
  3. Compute HMAC-SHA1 over the step-2 string using the Secret key from the console.
  4. Append the first 20 chars of the hash as encoded=: …&stime=20081201060100&etime=20081201183000&encoded=<first-20-chars-of-hash>

Token Schema V2​

Path-scoped token signed with MD5. The signature is the MD5 hash of the three space-joined fields expire fpath secret, encoded as URL-safe Base64 and placed — together with the expiry — as leading path segments. No IP and no App ID / Authorization header are involved.

  • expire — expiry as a UNIX timestamp; access after it is rejected with 403.
  • fpath — the first path segment of the resource, included in the signature.
  • secret — the console Secret key (never sent to the client).
  • resource — the remaining path after the first segment; it is served but not signed.

Algorithm

  1. Split the resource path into fpath (first segment) and resource (the rest).
  2. token = base64( md5( "<expire> <fpath> <secret>" ) ).
  3. Make it URL-safe: + → -, / → _, strip = padding.
  4. Build the URL: https://<host>/<token>/<expire>/<fpath>/<resource>.
const crypto = require('crypto');

const url = new URL('https://<your-cdn-domain>/vod/movie/index.m3u8');
const secret = process.env.CDN_TOKEN_SECRET; // console Secret key — keep server-side
const expire = 2782525307; // UNIX timestamp (see token types below)

// First path segment is signed; the rest is the resource.
const parts = url.pathname.split('/').filter(Boolean);
const fpath = parts[0];
const resource = parts.slice(1).join('/');

const digest = crypto.createHash('md5')
.update(`${expire} ${fpath} ${secret}`, 'utf-8')
.digest('base64');

const token = digest.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');

const signed = `${url.protocol}//${url.host}/${token}/${expire}/${fpath}/${resource}`;

Token types​

The hash is identical (md5("<expire> <fpath> <secret>")); the two types differ in how you choose expire and in the URL shape.

Fixed — expire is a far-future constant, so the signed URL is effectively permanent. The token is a single leading segment. Generate it once and reuse it.

https://<host>/<token>/<expire>/<fpath>/<resource>

Dynamic — expire is computed per request/session and the token segment carries a :<limit> suffix, where limit is the number of segments in fpath (maximum 5 — a deeper path is rejected). Sign a fresh token each time so links expire on their own.

const crypto = require('crypto');

const SECRET = process.env.CDN_TOKEN_SECRET; // console Secret key
const EXPIRES = Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60; // e.g. now + 30 days
const FPATH = 'vtvgo'; // may be multi-segment

const LIMIT = FPATH.split('/').length; // segment count
if (LIMIT > 5) throw new Error('FPATH exceeds the maximum of 5 path segments.');

const digest = crypto.createHash('md5')
.update(`${EXPIRES} ${FPATH} ${SECRET}`, 'utf8')
.digest('base64');
const token = digest.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

// Note the `<token>:<limit>` segment.
const url = `https://<host>/${token}:${LIMIT}/${EXPIRES}/${FPATH}/<resource>`;

If validation fails the edge rejects the request with HTTP 403.

Canal token​

Session/ACL token signed with HMAC-SHA256, injected as a __token__ path prefix. Required fields, joined with ~:

  • id — session ID (a user ID or a viewing session), formatted as a UUID v1 (RFC 4122).
  • exp — expiry as a UNIX timestamp (access before/after this is blocked).
  • acl — access path. Use * to grant everything below a prefix; sign multiple paths at once by joining them with !. For https://dsh-live.vnetwork.dev/live/disk/6ter-hd/dash-fhddvr/6ter-hd.mpd an ACL of /live/disk/6ter-hd/* covers the manifest and all its chunks.
  • hmac — hex digest of the fields above joined together.

Step 1 — collect the values:

id = 20e3c58b-f707-4900-bc55-fd237aed523c
exp = 1667845428
acl = /live/disk/6ter-hd/*

Joined: exp=1667845428~acl=/live/disk/6ter-hd/*~id=20e3c58b-f707-4900-bc55-fd237aed523c

Step 2 — HMAC-SHA256 the joined string with your Private key:

echo -n 'exp=1667845428~acl=/live/disk/6ter-hd/*~id=20e3c58b-f707-4900-bc55-fd237aed523c' \
| openssl dgst -sha256 -hmac '<PRIVATE_KEY>' -hex
# SHA2-256(stdin)= cf5df6ba…01b556f

Step 3 — append the digest as ~hmac=<digest>:

exp=1667845428~acl=/live/disk/6ter-hd/*~id=20e3c58b-f707-4900-bc55-fd237aed523c~hmac=cf5df6ba…01b556f

Step 4 — URI-encode the step-3 string, then prefix it with __token__ on the request path:

https://<your-cdn-domain>/__token__exp%3D1667845428~acl%3D%2Flive%2Fdisk%2F6ter-hd%2F%2A~id%3D20e3c58b-f707-4900-bc55-fd237aed523c~hmac%3Dcf5df6ba…01b556f/live/disk/6ter-hd/…/6ter-hd.mp4

On a valid token the edge removes __token__ and fetches from origin. On failure it returns HTTP 403 with a JSON body:

{ "reason": "TOKEN_EXPIRED" }
Error codeMeaning
TOKEN_EXPIREDThe token is past its exp.
TOKEN_INVALIDMalformed token, or the token does not match the requested resource.

Revoke tokens (BLOS)​

You can invalidate an issued token before it expires — useful when a session must be cut off while the protected content is being accessed. Send the session IDs to the block API:

  • URL — https://cdn-api.swiftfederation.com/v2.0/services/{serviceId}/blos
  • Method — POST
  • Body — tokens: string array of session tokens to blacklist.
{
"tokens": ["20e3c58b-f707-4900-bc55-fd237aed523c"]
}

Next steps​