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.
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 algorithm | Scheme | Hash | Typical use |
|---|---|---|---|
HMAC-SHA256 | Canal token | HMAC-SHA256 | Current default — session/ACL tokens for VOD & live |
HMAC-MD5 (deprecated) | Token Schema V2 | MD5 | Standard CDN secure-link scripts |
| — | Basic token | HMAC-SHA1 | Legacy 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),
yyyymmddHHMMSSUTC (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:
- Strip method + hostname →
/path/to/resource?clientId=12345&product=A123. - Append the mandatory time fields:
/path/to/resource?clientId=12345&product=A123&stime=20081201060100&etime=20081201183000 - Compute
HMAC-SHA1over the step-2 string using the Secret key from the console. - 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
- Split the resource path into
fpath(first segment) andresource(the rest). token = base64( md5( "<expire> <fpath> <secret>" ) ).- Make it URL-safe:
+→-,/→_, strip=padding. - 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!. Forhttps://dsh-live.vnetwork.dev/live/disk/6ter-hd/dash-fhddvr/6ter-hd.mpdan 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 code | Meaning |
|---|---|
TOKEN_EXPIRED | The token is past its exp. |
TOKEN_INVALID | Malformed 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
- Domain configuration → Access Control — enable tokens and create the secret.
- Multi-CDN overview — the product at a glance.