Protect your content with signed URLs

Prevent unauthorized viewers from downloading your content or embedding your player on sites that you do not own.

JW Player enables you to create expiring, signed URLs to secure your content and player. Using signed URLs for your public content or player prevents unauthorized viewers from downloading your content or embedding your player on sites that you do not own.

To enable URL signing on your media content and players, you must complete two general processes:

  1. Create signed (non-JWT or JWT) URLs.
  2. Enable URL signing functionality

Before explaining how to implement URL signing, the next section explains the types of signed URLs that JW Player supports.


Types of signed URLs

JW Player's Delivery API supports two methods of signing URLs:

  • Signed non-JWT (non-JWT)
  • JSON web token (JWT) tokenized

Non-JWT URLs apply when you reference any of JW Player's v1 endpoints and routes. JWT URLs apply when you reference any of the JW Player's v2 endpoints and routes.

The following table identifies when to use each type of signed URL.

Signed URL typeApplicable routes
non-JWTPlayers*
  • /libraries/player_id.js
  • /players/{content_id}-{player_id}.embed_type
  • /previews/{content_id}-{player_id}

Poster image
  • /thumbs/{media_id}-thumb_width.jpg

Streaming manifests
  • /manifests/{media_id}.manifest_extension

Text tracks
  • /tracks/{track_id}.track_extension

Video files
  • /videos/{media_id}-{template_id}.media_extension
JWTAdvertising schedule*
  • /v2/advertising/schedules/ad_schedule_id.ad_schedule_extension

Media
(This includes streaming manifests, text tracks, and video files)
  • /v2/media/media_id

Playlist
  • /v2/playlists/playlist_id

Poster image
  • /v2/media/media_id/poster.jpg

* Denotes content that can be accessed only through this route of the Delivery API.


Create a signed non-JWT URL

To create a non-JWT URL, you must append two parameters to the URL of the content or player: exp and sig.

http://cdn.jwplayer.com/videos/nPripu9l.mp4?exp=1371335018&sig=a0124258c73177029d09bb82c6608392
ParameterDescription
expExpiration date of the URL as a UNIX timestamp, for example, 1271338236

Typically, generated URLs should be valid between a minute and a few hours.

The shorter you make the duration until an expiration date, the more secure you make your content. Once a link has expired, even download tools will not be able to grab the content. However, overly short expirations can result in bad user experience due to small discrepancies in server time or delays in clients requesting resources.
sigSignature used to authorize the request

See: sig parameter.

sig parameter

The signature (sig) is an MD5 digest of the path, the expiration date, and the account secret: md5(CONTENT_PATH:EXPIRATION_STAMP:ACCOUNT_SECRET).

👍

IMPORTANT

When setting the EXPIRATION_STAMP for https://content.jwplatform.com/players/{content_id}-{player_id}}.html?sig={sig}&exp={exp} and https://content.jwplatform.com/previews/{content_id}-{player_id}.html?sig={sig}&exp={exp}, JW Player only enforces protection up to three hours in the future. For DRM'd content, the protection window will be the same as the DRM policy's License Duration.

ParameterDescription
ACCOUNT_SECRETProperty secret

Use the following steps to locate your property secret:
   1. From your JW Player dashboard, click the gear next to your name > API Credentials.
   2. In the JW Platform API Credentials section, click SHOW CREDENTIALS next to a property name.
   3. Copy the Secret.
CONTENT_PATHOnly the path portion of the URL without the domain or leading slash, for example, videos/nPripu9l.mp4
EXPIRATION_STAMPExpiration date of the URL as a UNIX timestamp, for example, 1271338236

Generate the non-JWT signed URL

The following code examples show approaches to programmatically generate a non-JWT signed URL. Links are valid for 1 hour and must be normalized to 5 minutes to promote better caching.

👍

IMPORTANT

The following samples are provided for guidance and may not work in your environment. If you use any of these samples, be sure to test the functionality in a development environment before deploying it into a production environment.

const MD5 = require('crypto-js/md5');
const Time = new Date();

const API_SECRET = 'V1APISECRET';

/**
 * Returns a signed url, can be used for any "non-JWT" endpoint
 * @param {string} path
 * @param {int} expires 
 * @param {string} secret 
 * @param {string}host 
 * @returns {string} A signed url
 */
function signed_url(
    path,
    expires = 6000,
    secret = API_SECRET,
    host = 'https://cdn.jwplayer.com'
) {
    const base = `${path}:${expires}:${secret}`;
    const signature = MD5(base);
    return `${host}/${path}?exp=${expires}&sig=${signature}`;
}

/**
 * Return signed url for the single line embed javascript
 * @param {string} mediaid The media id (also referred to as video key)
 * @param {string} playerid The player id (also referred to as player key)
 * @returns 
 */
function get_response(mediaid, playerid) {
    const path = `players/${mediaid}-${playerid}.js`;
    const expires = Math.ceil((Time.getTime() + 3600) / 300) * 300;
    return signed_url(path, expires);
}

console.log(get_response('MEDIAID', 'PLAYERID'));
import hashlib
import math
import time


API_SECRET = os.environ["JWPLATFORM_API_SECRET"]  # Replace

def signed_url(path, expires, secret=API_SECRET, host="https://cdn.jwplayer.com"):
    """
    returns a signed url, can be used for any "non-JWT" endpoint
    Args:
      path(str): the jw player route
      expires(int): the expiration time for the URL
      secret(str): JW account secret
      host:(str): url host
    """
    s = "{path}:{exp}:{secret}".format(path=path, exp=str(expires), secret=API_SECRET)
    signature = hashlib.md5(s.encode("utf-8")).hexdigest()
    signed_params=dict(exp=expires, sig=signature)
    return "{host}/{path}?{params}".format(
        host=host, path=path, params=urllib.parse.urlencode(signed_params)
    )

    
def get_signed_player(media_id, player_id):
    """
    Return signed url for the single line embed javascript.

    Args:
      media_id (str): the media id (also referred to as video key)
      player_id (str): the player id (also referred to as player key)
    """
    path = "players/{media_id}-{player_id}.js".format(
        media_id=media_id, player_id=player_id
    )

    # Link is valid for 1 hour but normalized to 5 minutes to promote better caching
    expires = math.ceil((time.time() + 3600) / 300) * 300

    # Generate signature
    return signed_url(path, expires)

media_id = "YOUR MEDIA ID"  # Replace
player_id = "YOUR PLAYER ID"  # Replace
signed_url = get_signed_player(media_id, player_id)
print(signed_url)
print(
    "<script type='text/javascript' src='{signed_url}'></script>".format(
        signed_url=signed_url
    )
)


Create a signed JWT URL

To create a JWT URL, you must append a token parameter to the URL of the content or player. The token parameter is comprised of three Base64-URL strings separated by dots that can be easily passed in HTML and HTTP environments.

To generate the token parameter you need a header, payload, and signature. The next three subsections define the header, payload, and signature. Then, the Generate the JWT signed URL subsection provides code examples to generate the JWT signed URL.


Header

The header specifies the cryptographic algorithm and token type. JW Player currently supports a single algorithm and token type. All headers should use the following:

{
  "alg": "HS256",
  "typ": "JWT"
}
PropertyDescription
alg string(Required) Signing algorithm used

This is always HMAC SHA256 (HS256).
typ string(Required) Type of token

This is always JWT.

Payload

The payload consists of claims that specify a resource (resource) being requested, an expiration time (exp), and any parameters the route accepts. All URL parameters that you want to include must be included in the payload. Any URL parameter added to a JWT-signed request will be ignored if it is not within the payload.

The following code example includes related_media_id as an additional parameter.

{
  "resource": "/v2/playlists/Xw0oaD4q",
  "exp": 1893456000,
  "related_media_id": "RltV8MtT"
}
PropertyDescription
exp number(Required) Expiration date of the token, as a UNIX timestamp

Generated URLs should be valid between a minute and a few hours.

The shorter you make the duration until an expiration date, the more secure you make your content. Once a link has expired, even download tools will not be able to grab the content. However, overly short expirations can result in bad user experience due to small discrepancies in server time or delays in clients requesting resources.

If you have a high-volume website, you should cache signed URLs (for example in intervals of 5 minutes) to prevent performance issues that may occur due to generating signed URLs. Signed requests do not have to be unique.
resource string(Required) Content or player that is being requested

This can be a relative or absolute URL. This property ensures that generated tokens cannot be applied to unintended resources.

Signature

The signature is comprised of the encoded header, the encoded payload, and the property secret.

Use the following steps to locate your property secret:

  1. From your JW Player dashboard, click the gear next to your name > API Credentials.
  2. In the v1 API Credentials section, click SHOW CREDENTIALS next to a property name.
    3, Copy the Secret.

Generate the JWT signed URL

The following code examples show approaches to programmatically generate a JWT signed URL. Links are valid for 1 hour and must be normalized to 6 minutes to promote better caching.

👍

IMPORTANT:

The following samples are provided for guidance and may not work in your environment. If you use any of these samples, be sure to test the functionality in a development environment before deploying it into a production environment.

const jwt = require('jsonwebtoken');
const Time = new Date();

const API_SECRET = 'V1APISECRET';

/**
 * Generatea a URL with signature.
 * @param {string} path
 * @param {string} host 
 * @returns {string} signed URL
 */
function jwt_signed_url(path, host = 'https://cdn.jwplayer.com') {
    const token = jwt.sign(
        {
            exp: Math.ceil((Time.getTime() + 3600) / 300) * 300,
            resource: path,
        },
        API_SECRET
    );

    return `${host}${path}?token=${token}`;
}

const media_id = 'MEDIAID';
const path = `/v2/media/${media_id}`;
const url = jwt_signed_url(path);

console.log(url);
import math
import time
import urllib.parse

# Requires installing third party packages:
# $ pip install python-jose requests
from jose import jwt
import requests

API_SECRET = os.environ["JWPLATFORM_API_SECRET"]

def jwt_signed_url(path, host="https://cdn.jwplayer.com"):
    """
    Generate url with signature.
    Args:
      path (str): url path
      host (str): url host
    """
    
    # Link is valid for 1 hour but normalized to 6 minutes to promote better caching
    exp = math.ceil((time.time() + 3600) / 300) * 300
    
    params = {}
    params["resource"] = path
    params["exp"] = exp
    
    # Generate token
    # note that all parameters must be included here
    token = jwt.encode(params, API_SECRET, algorithm="HS256")
    url = "{host}{path}?token={token}".format(host=host, path=path, token=token)
    
    return url
  
media_id = "YOUR MEDIA ID"  # Replace
path = "/v2/media/{media_id}".format(media_id=media_id)
url = jwt_signed_url(path)

r = requests.get(url)
print(r.json())


Enable URL signing functionality

After you have created signed URLs for all of your content, you must enable URL signing functionality for your properties.

Content Protection tab within the JW Player dashboard.Content Protection tab within the JW Player dashboard.

Content Protection tab within the JW Player dashboard.

  1. From the Properties page of your JW Player dashboard, click the name of the property. The settings page for the property appears.
  2. On the Content Protection tab in the URL Signing section at the bottom, click the toggle to Secure Video URLs or Secure Player Embeds. Depending on your use case, you can also enable both settings at the same time.
  3. Click SAVE.


Error handling

Error codeError messagePossible conditions
200SuccessContent is requested via a signed URL when URL signing is enabled for all publisher content.
403Access forbiddenContent is requested via an unsigned URL when URL signing is enabled for all publisher content.

Content is requested via an incorrectly signed URL when URL signing is enabled for all publisher content.


Did this page help you?