Skip to main content
By default, videos are available by their links with no restrictions and can be placed on any website or in any mobile application. But in some cases, platform owners or rights holders need to ensure that video content does not offer public access, such as when a video is only for paid access, private access, temporary access, or access on a specific site.

When to use secure tokens (and when NOT to)

You need to control WHO can access your content:
  • Paid content/Paywalls: Only authenticated, paying users should access videos
  • Private videos: Content for logged-in users only (educational platforms, corporate training)
  • Time-limited access: Preview links that expire after X hours/days
  • Anti-hotlinking: Prevent other websites from embedding your videos without permission
  • Per-user access control: Generate unique tokens for each user with your backend
Example use cases:
  • Online course platform: Students must log in to watch lessons
  • VOD platform: Users pay subscription to access movies
  • Event streaming: Generate one-time links for ticket holders
  • Content preview: Share 24-hour preview links with partners
Important: Secure tokens require backend development to generate tokens for each user/request. If you just need geo-blocking or domain restrictions, use the simpler CDN security policies instead.

How does Gcore protect video content?

Gcore has several methods of protecting video content:
  1. Protected temporary links with secure token (this article) - Controls WHO can access
  2. Country access policy (geo-restrictions) - Controls WHERE content is available
  3. Referrer validation - Controls which websites can embed
  4. AES-128 encryption - Encrypts video content
We plan to add more types of protections in the future, such as watermarking. Contact us via support@gcore.com if you want to suggest additional options.

Quick decision guide: Which protection method do I need?

Your requirementProtection methodComplexity
”Only allow viewers in specific countries”Country access policy⭐ Easy (CDN setting)
“Only allow embedding on my website”Referrer validation⭐ Easy (CDN setting)
“Only paid/logged-in users can watch”Secure tokens (this article)⭐⭐⭐ Advanced (requires backend)
“Encrypt video files for DRM”AES-128 encryption⭐⭐⭐ Advanced (requires key management)
“Prevent unauthorized downloads”Combine secure tokens + AES-128⭐⭐⭐ Advanced
Most common scenario: If you just need to restrict content by country (e.g., “only available in Dominican Republic for broadcasting rights”), use Country access policy—not secure tokens. It’s much simpler and doesn’t require backend development.
In this article, we will look at the first option: protected temporary links and, relatedly, Secure Tokens. Protected temporary links are generated using the CDN-resource Secure Token feature, which allows configuring access with tokenized URLs. When using this option, you add a special character set to every URL. Check out the examples below with the special characters highlighted in bold. At Gcore, we call these special characters Secure Tokens. VOD: LIVE: Unlike content with a public URL, which is available to any user, content with a protected temporary link verifies that the user has a special hash key that matches the generated token. There are three possible scenarios:
  • HTTP 2xx response code, if the hash key is valid and unexpired
  • HTTP 403 Forbidden response, if the hash key is invalid
  • HTTP 410 Gone response, if the hash key is valid but expired
Here is an example of what happens if we try to access the video when the token is expired:
curl -I https://demo-protected.gvideo.io/videos/2675_pG8TfmKx2LU2qs/2oY5gKMSVBBX2x-VbzN03g/1755007200/master.m3u8

HTTP/2 410 
date: Tue, 12 Aug 2025 15:15:18 GMT
content-type: text/html
content-length: 136

CDN resources and scope of restrictions

Video content hosted via the Video Streaming is viewed and delivered through our CDN, so by default your account has only one hidden CDN resource in the format ******.gvideo.io. The restriction policies, such as the Secure Token for links protection, apply specifically to CDN resources because a CDN resource covers all video content at once. This means you don’t need to create rules and make permissions for each video separately—it is enough to create and apply permissions only once to the CDN resource.

Multiple CDN resources for different policies

Your account can have several CDN resources leading to the same content (origin). These CDN resources can have different access policies. This allows you to combine:
  • Open access on one CDN resource (e.g., public.gvideo.io)
  • Protected access on another CDN resource (e.g., protected.gvideo.io)
  • Geo-restricted access on a third resource (e.g., us-only.gvideo.io)
Example configurations:
CDN resourceSecurity policyUse case
demo-protected.gvideo.ioSecure tokens (no IP binding)Paid content accessible from any location
demo-protected-ip.gvideo.ioSecure tokens (with IP binding)Enhanced security with IP validation
demo-geo.gvideo.ioCountry access policyContent available only in specific countries
demo-public.gvideo.ioNo restrictionsFree, public content
To create custom CDN resources: Contact support team or follow the CDN resource creation guide. Support will help configure vp_rules to route your streaming content correctly.

How to enable the secure token feature?

To enable the Secure Token feature and start protecting your links, please check out the CDN page how to activate a secure token for CDN-resource.
To enable IP-based secure tokens, turn on the “Add client IP to token” option. Disable this option if IP binding is not required.

Secure token for HLS/DASH

Secure token – protected temporary links have the following format:
https://domain.com/videos|cmaf|mpegts/{client_id}_{video_id}/{token}/{expiration}/manifest.m3u8
Where:
  • videos – for VOD, cmaf|mpegts – for LIVE
  • {client_id} is your account ID
  • {video_id} is the identifier of the video or live stream
  • {token} is the MD5 hash of the video and other attributes
  • {expiration} is a Unix timestamp (in seconds) that defines until when the link remains valid
For Gcore Video on Demand (VOD) and Gcore Live Streaming, a relative path is used for all file types used to deliver video content, such as additional manifests, chunks, and MP4 renditions. Therefore, the path of the main manifest is taken as the basis and the following path is built relative to it. Examples of templates of protected links for VOD:
  • https://domain.com/videos/{client_id}_{video_slug}/{token}/{expiration}/manifest.m3u8
  • https://domain.com/videos/{client_id}_{video_slug}/{token}/{expiration}/segment-1-svod720n-v1-a1.ts
  • https://domain.com/videos/{client_id}_{video_slug}/{token}/{expiration}/720.mp4
Examples of templates of protected links for LIVE:
  • https://domain.com/cmaf/{client_id}_{stream_id}/{token}/{expiration}/master.m3u8
  • https://domain.com/cmaf/{client_id}_{stream_id}/{token}/{expiration}/index.mpd
  • https://domain.com/cmaf/{client_id}_{stream_id}/{token}/{expiration}/004chunk-stream4-10000000-05213.m4s?part=4

Advanced secure token for MP4

A regular secure token for HLS/DASH from above protects the entire video entity. When distributing MP4 files separately from ABR, use an MP4 advanced secure token as an additional query parameter. This restricts access to the specific MP4 rendition and prevents unauthorized MP4 distribution.
Advanced secure token for MP4 cannot be combined with generic secure token for HLS/DASH. In case of combination the secure token for HLS/DASH will have priority to resolve an accees.
Advanced secure token for MP4 – protected temporary links have the following format:
https://domain.com/videos/client_id_video_id/filename.mp4?md5={token}&expires={expiration}
Where:
  • {token} is the MD5 hash of the MP4 file path and other options
  • {expiration} is a Unix timestamp (in seconds) that defines until when the link remains valid

A note on the expiration time

The expiration time must be at least equal to the duration of the original video or the expected duration of the live playback. When the signed URL expires, URLs will no longer be played, even if playback has already begun. Because video expiration time is integrated into the URL described above, new chunks will no longer be given by the relative path. Your app also needs to handle cases where a user starts to play a video, then leaves your app for a long time, and then comes back later and tries to play the video again. You will probably need to detect this behavior and reacquire the new signed URL to make sure playback can start. To handle both cases, ensure you set the expiration far enough into the future that users won’t experience playback interruptions.
expires = 1861919999 # Sunday, December 31, 2028 23:59:59 GMT

Create secure token for HLS/DASH

Pattern to generate token:
  1. Tokens without user ip:
${client_id}_${video_id}_${secret}_${expires}_
  1. Tokens with user ip:
${client_id}_${video_id}_${secret}_${expires}_${user_ip}
ParameterRequiredDescription
client_idyesURL of the MP4 file
video_idyesVideo_Slug or Stream_ID identifier
secretyesSecret phrase
expiresyesExpiration time (Unix timestamp, UTC)
user_ipoptionalClient IP address. Required only if token was generated with IP binding
import base64
from hashlib import md5
from time import time

def gethash(client_id, video_id, secret, expires):
  hash_body = "%s_%s_%s_%s_" % (client_id, video_id, secret, expires)   # set of unique parameters of video
  hash_md5 =  base64.b64encode(                                         # get MD5 hash from parameters of video
      md5(hash_body.encode()).digest()                            
    ).decode().replace("+", "-").replace("/", "_").replace("=", "")   # preparation for use in URL 
  return hash_md5

client_id = "2675"       # enter your account ID here
secret = ""              # enter your secret key from CDN-resource here

#VOD
video_slug = "3dk4NsRt6vWsffEr"     # enter your video's slug here
expires = int(time()) + 24*60*60    # 24 hours, unixtime in seconds

token = gethash(client_id, video_slug, secret, expires)
print(f"https://demo-protected.gvideo.io/videos/{client_id}_{video_slug}/{token}/{expires}/master.m3u8")

#LIVE
stream_id = "201693"                      # enter your live stream id here
expires = int(time()) + 24*60*60    # 24 hours, unixtime in seconds

token = gethash(client_id, stream_id, secret, expires)
print(f"https://demo-protected.gvideo.io/cmaf/{client_id}_{stream_id}/{token}/{expires}/master.m3u8")

Create advanced secure token for MP4

The MP4 advanced secure token is generated from the full MP4 URL. You can optionally bind MP4 speed limit to the token by including the speed and buffer parameters. This ensures that download rate limits cannot be bypassed – if a user tampers with these values, the request will return 403 Forbidden. If no speed limit is required, set both speed and buffer to an empty string when generating the token. Pattern to generate token:
  1. Tokens without user ip:
${uri}_${secret}_${expires}_${speed}_${buffer}_
  1. Tokens with user ip:
${uri}_${secret}_${expires}_${speed}_${buffer}_${user_ip}
ParameterRequiredDescription
uriyesPath from MP4 file URL
secretyesSecret phrase
expiresyesExpiration time (Unix timestamp, UTC)
speedoptionalDownload speed limit in bytes/sec. Use an empty string if not required
bufferoptionalBuffer size in bytes for rate limiting. Use an empty string if not required
user_ipoptionalClient IP address. Required only if token was generated with IP binding
Examples: Secret: MySecr3tStr1ng Without user ip:
string to sign: /videos/279481_1053391/qid2920v1_h264_4050_1080_1.mp4_MySecr3tStr1ng_1743167062___
url: http://gvideo.dev/videos/279481_1053391/qid2920v1_h264_4050_1080_1.mp4?md5=QX39c77lbQKvYgMMAvpyMQ&expires=1743167062

string to sign: /videos/279481_1053391/qid2920v1_h264_4050_1080_1.mp4_MySecr3tStr1ng_1743167062_1000k__
url: http://gvideo.dev/videos/279481_1053391/qid2920v1_h264_4050_1080_1.mp4?md5=UcB_lMku9m-MtxIEos_V-w&expires=1743167062&speed=1000k

string to sign: /videos/279481_1053391/qid2920v1_h264_4050_1080_1.mp4_MySecr3tStr1ng_1743167062_1000k_2000k_
url: http://gvideo.dev/videos/279481_1053391/qid2920v1_h264_4050_1080_1.mp4?md5=yGa6cK0jM4YvwaSUEPvwMA&expires=1743167062&speed=1000k&buffer=2000k
With user ip 1.2.3.4
string to sign: /videos/279481_1053391/qid2920v1_h264_4050_1080_1.mp4_MySecr3tStr1ng_1743167062___1.2.3.4 
url: http://gvideo.dev/videos/279481_1053391/qid2920v1_h264_4050_1080_1.mp4?md5=SZMnuXQv-81sVqslGtXwlA&expires=1743167062 

string to sign: /videos/279481_1053391/qid2920v1_h264_4050_1080_1.mp4_MySecr3tStr1ng_1743167062_1000k__1.2.3.4 
url: http://gvideo.dev/videos/279481_1053391/qid2920v1_h264_4050_1080_1.mp4?md5=fhhV7JWE7c1HGjD7Vu5XsA&expires=1743167062&speed=1000k 

string to sign: /videos/279481_1053391/qid2920v1_h264_4050_1080_1.mp4_MySecr3tStr1ng_1743167062_1000k_2000k_1.2.3.4 
url: http://gvideo.dev/videos/279481_1053391/qid2920v1_h264_4050_1080_1.mp4?md5=by-olfoRZ3-Nntli7_2Ecw&expires=1743167062&speed=1000k&buffer=2000k
import base64
from hashlib import md5
from time import time
from datetime import datetime, timezone, timedelta

def gethash_mp4(uri, secret, expires, speed, buffer, ip):
    hash_body = "%s_%s_%d_%s_%s_%s" % (uri, secret, expires, speed, buffer, ip)   # set of unique parameters of video
    hash_md5 =  base64.b64encode(                                                 # get MD5 hash from parameters of video
        md5(hash_body.encode()).digest()
    ).decode().replace("+", "-").replace("/", "_").replace("=", "")   # preparation for use in URL
    return hash_md5

secret = ""                                             # enter your secret key from the CDN-resource
uri = "/videos/2675_pG8TfmKx2LU2qs/qid3570v1_h264_1800_720.mp4"   # path of the MP4 file

# current time
time_now = datetime.now(timezone.utc)
expires = int(time()) + 24 * 60 * 60            # expiration = now + 24 hours, unixtime in seconds
expires_dt = datetime.fromtimestamp(expires, tz=timezone.utc)
time_format = "%Y-%m-%d %H:%M:%S %Z"
print("Now:     ", int(time_now.timestamp()), time_now.strftime(time_format))
print("Expires: ", expires, expires_dt.strftime(time_format))

# MP4 protected (the CDN resource must be with deactivated option "Add a client's IP to the token")
token = gethash_mp4(uri, secret, expires, "", "", "")
print(f"https://demo-protected.gvideo.io{uri}?md5={token}&expires={expires}")
print(f"https://demo-protected.gvideo.io{uri}/download?md5={token}&expires={expires}")
print(f"https://demo-protected.gvideo.io{uri}/download=my-awesome-video?md5={token}&expires={expires}")

# MP4 with speed limit
speed = "1M"
token = gethash_mp4(uri, secret, expires, speed, "", "")
print(f"https://demo-protected.gvideo.io{uri}?md5={token}&expires={expires}&speed={speed}")

# MP4 with speed and buffer limit
speed = "500K"
buffer = "10M"
token = gethash_mp4(uri, secret, expires, speed, buffer, "")
print(f"https://demo-protected.gvideo.io{uri}?md5={token}&expires={expires}&speed={speed}&buffer={buffer}")

# MP4 protected with user's IP address (the CDN resource must be with activated option "Add a client's IP to the token")
ip = "92.223.112.84"
token = gethash_mp4(uri, secret, expires, "", "", ip)
print(f"https://demo-protected-ip.gvideo.io{uri}?md5={token}&expires={expires}")
print(f"https://demo-protected-ip.gvideo.io{uri}/download?md5={token}&expires={expires}")
print(f"https://demo-protected-ip.gvideo.io{uri}/download=my-awesome-video?md5={token}&expires={expires}")

# MP4 protected with user's IP address and speed limit
speed = "1000K"
token = gethash_mp4(uri, secret, expires, speed, "", ip)
print(f"https://demo-protected-ip.gvideo.io{uri}?md5={token}&expires={expires}&speed={speed}")

# MP4 protected with user's IP address, speed and buffer limit
speed = "500K"
buffer = "10M"
token = gethash_mp4(uri, secret, expires, speed, buffer, ip)
print(f"https://demo-protected-ip.gvideo.io{uri}?md5={token}&expires={expires}&speed={speed}&buffer={buffer}")

Python playground – https://www.onlineide.pro/playground/share/d6126cff-e97f-456b-b667-6a95030563b4