Protect your content with signed URLs

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

JWP 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

πŸ’‘

For additional information about video URL signing by content and players, see Secure signing behavior.



Types of Signed URLs

The Delivery API supports two methods of signing URLs:

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

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

SigningΒ Type Description
JWT Applies when you reference any v2 endpoints and routes

Advertising Schedule1 Media
  • /v2/media/media_id
    The response includes signed video URLs. Unsigned URLs are returned when requested without a signature to prevent unauthorized video access.
Playlist
  • /v2/playlists/playlist_id
    The response includes signed video URLs. Unsigned URLs are returned when requested without a signature to prevent unauthorized video access.
Poster Image
Non-JWT Applies when you reference any v2 endpoints and routes

Players1 Poster Image Streaming Manifests Text Tracks Video Files

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



Signed URL Creation


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 include an object that contains alg and typ.

{
  "alg": "HS256",
  "typ": "JWT"
}
Parameter Description
alg* string Signing algorithm used

This is always HMAC SHA256 (HS256)
type* string 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.

🚧

Be sure that all UNIX timestamps are in seconds. Smaller units of time (such as milliseconds) will not be interpreted correctly.

{
  "resource": "/v2/playlists/Xw0oaD4q",
  "exp": 1893456000,
  "related_media_id": "RltV8MtT"
}
Parameter Description
exp* number Expiration date of the token, as a UNIX timestamp in seconds, for example, 1271338236.

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 a 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 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.

🚧

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_V1API_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())


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
Parameter Description
exp* Expiration date of the URL as a UNIX timestamp in seconds, 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 a bad user experience due to small discrepancies in server time or delays in clients requesting resources.
sig* Signature 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)
Property Description
ACCOUNT_SECRET Property secret from the v1 API

Use the following steps to locate your property secret:
  1. From your JWP 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_PATH Only the path portion of the URL without the domain or leading slash, for example, videos/nPripu9l.mp4
EXPIRATION_STAMP Expiration date of the URL as a UNIX timestamp in seconds, for example, 1271338236

πŸ“˜

When setting the EXPIRATION_STAMP for https://cdn.jwplayer.com/players/{content_id}-{player_id}}.html?sig={sig}&exp={exp} and https://cdn.jwplayer.com/previews/{content_id}-{player_id}.html?sig={sig}&exp={exp}, JWP only enforces protection up to three hours in the future.

For DRM content, the protection window will be the same as the DRM policy's License Duration.


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.

🚧

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_V1API_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
    )
)


Enable URL signing functionality

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

616

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.


FAQ

Does URL token signing work the same for JWP hosted and externally hosted (registered) media?

No.

For JW Platform hosted media, both the request to the Delivery API and all media URLs will be signed.

For externally hosted (registered) media, only the request to the Delivery API and the returned media URL will be signed. The signed media URL from the Delivery API response will redirect (HTTP status code 302) to the externally hosted media URL. JW Platform will not sign the externally hosted media URL.