# Secure video playback
In this guide you will learn how to use signed URLs for securing video playback.
If you add an asset or start a live stream through Mux without passing a playback policy, you'll be unable to access it in your browser using a URL. This may seem counterintuitive at first, however, this gives you the ability to be explicit about who can access your content, as well as exactly how, where, or when they can access it.

There may be instances where you upload a video to Mux that is not intended to be made available for public viewing. For example, maybe you have a membership site that your users must join to access your videos, or you are offering a pay-to-access live stream.

For these scenarios, Mux offers **playback policies** that allow you to control the different ways users can view and interact with your content.

## Understanding playback policies

When you upload a video or initiate a live stream through Mux, you also have the option to define what type of fine-grained access should apply to your content. This is done by specifying a playback policy.

Mux offers two kinds of playback policies: `public` and `signed`.

* **Public** playback policies will enable playback URLs that can be watched anywhere, at any time, without any restrictions. This option is perfect for sharing your viral cat videos with the whole world.
* **Signed** playback policies will enable playback URLs that require a valid JSON Web Token (JWT) to gain access. The JWT should be signed and generated by your application on a protected server, not on a public client.

A playback policy can be specified when you create a new asset or live stream, or can be added to an existing asset or live stream.

Once an asset or live stream has been assigned a playback policy, the asset will be issued a new playback ID that's associated with its corresponding playback policy. It's possible for each asset or live stream to have multiple playback IDs.

See <ApiRefLink href="/docs/api-reference/video/assets/create-asset-playback-id">Create a playback ID</ApiRefLink> to learn how to add a new playback policy and ID to an existing Asset or Live Stream.

**Public** playback policies are pretty self-explanatory, so let’s dig into the signed playback policies.

## A closer look at signed playback policies

When you apply a signed playback policy to your content, there are two distinct ways you can restrict video playback:

1. **Expiration time** (required) allows you to specify a point in time when your issued JWT should be considered expired. Viewers with a valid token can watch videos until your specified expiration time value passes. All HTTP requests made to access your content past the expiration time are denied.
2. **Playback Restrictions** (optional) allow you to implement additional rules for playing videos. For example, let’s consider Referrer Validation. When you create a signed playback policy, you can supply a list of websites that are allowed to host and serve your content. Any requests from domains that aren't on the allow list are denied if they attempt to play back your content.

Referrer and User-Agent Validation Playback Restrictions are supported today; Mux plans to add more types of restrictions in the future.

Let’s walk through a typical workflow for creating a valid JWT used to access a Mux asset with a signed playback policy.

## 1. Create an Asset or Live Stream with a signed playback policy

Let’s start from scratch and add a new asset to our Mux account using a standard authenticated API call. Notice how we set the `playback_policy` value to `signed` during this **Create Asset** request:

```json
// POST https://api.mux.com/video/assets

{
  "inputs": [
    {
      "url": "https://storage.googleapis.com/muxdemofiles/mux-video-intro.mp4"
    }
  ],
  "playback_policies": [
    "signed"
  ],
  "video_quality": "basic"
}
```

## 2. Create a signing key for your Mux account environment

Next, we'll need to create a Mux signing key. Signing keys are used to generate valid JWTs for accessing your content. Signing keys can be managed (created, deleted, listed) from the [Signing Keys settings](https://dashboard.mux.com/settings/signing-keys) of the Mux dashboard or via the Mux System API.

<Callout type="info">
  Remember: Mux signing keys are different than Mux API keys.
</Callout>

When you create a new signing key, the API generates a 2048-bit RSA key-pair and returns the private key and a generated key-id. You should securely store the private key for signing the token, while Mux stores the public key to validate the signed tokens.

Signing keys are created and deleted independently of assets. You probably only need one signing key active at a time, but you can create multiple to enable key rotation, creating a new key and deleting the old one only after any existing signed URLs have expired.

See <ApiRefLink href="/docs/api-reference/system/signing-keys">Create a URL signing key</ApiRefLink> for full documentation.

```json
//POST https://api.mux.com/system/v1/signing-keys

{
  "data": {
    "private_key": "(base64-encoded PEM file with private key)",
    "id": "(unique signing-key identifier)",
    "created_at": "(UNIX Epoch seconds)"
  }
}
```

## 3. Create an optional Playback Restriction for your Mux account environment

Mux supports two types of playback restriction:

* Referrer Validation: Restricts whether the domain specified in the HTTP `Referer` request header or when no referrer domain is specified will be allowed for playback.
* User Agent Validation: Restricts whether a high risk user agent specified in the `User-Agent` request header or when no user agent is specifed will be allowed for playback.

During playback, a restriction is applied using a JWT claim, which will be covered in the next two sections.

<Callout type="info">
  Playback restrictions exist at the environment level. However, creating a playback restriction in an environment does not mean all assets are automatically restricted by it.

  Instead, you should apply a given restriction to a playback by referencing it in the token you create for a signed playback ID.
</Callout>

If you don’t need to use playback restrictions for your content, feel free to jump to the next step.

### Create a Playback Restriction

Most commonly, you want all the videos on your Mux account to be watched only on your website `https://example.com`. To do so, you can create a new Playback Restriction by adding `example.com` domain as the only allowed domain that can play your videos.

See <ApiRefLink href="/docs/api-reference/video/playback-restrictions">Playback Restriction</ApiRefLink> for full documentation.

### Example API Request

```json
//POST https://api.mux.com/video/v1/playback-restrictions

{
  "referrer": {
    "allowed_domains" : [
      "example.com"
	  ],
    "allow_no_referrer" : false
  }
}
```

### Example API Response

```json
{
  "data": {
    "updated_at": "1634595679",
    "referrer": {
      "allowed_domains": [
        "example.com"
      ],
    },
    "id": "JL88SKXTr7r2t9tovH7SoYS8iLBVsjZ2qTuFS8NGAQY",
    "created_at": "1634595679"
  }
}
```

Store the `id` value from the API response above as `PLAYBACK_RESTRICTION_ID` in your application for later use when generating the signed JWT.

### Playback Restriction Syntax

When you create a playback restriction, you may specify the referrer domains and/or user agent restrictions in the same request. The `referrer` field allows you to specify the referrer restrictions and the `user_agent` field allows you to specify the user agent restrictions. For the referrer `allowed_domains` list, you may specify up to 100 unique domains or subdomains where your videos will be embedded.  Specify this in the `referrer.allowed_domains` array using valid DNS-style wildcard syntax. For example:

```json
{
  "referrer": {
    "allowed_domains": [
      "*.example.com",
      "foo.com"
    ],
    "allow_no_referrer": false
  },
  "user_agent": {
    "allow_no_user_agent": false,
    "allow_high_risk_user_agent": false
  }
}
```

Choose from the following options:

* To deny video playback requests for all domains, use an empty Array: `[]`
* To allow playback on `example.com` and all the subdomains of `example.com`, use the syntax: `["*.example.com", "example.com"]`
* Use a single wildcard `*` entry to allow video playback requests from any domain: `["*"]`
* Use a wildcard for one subdomain level. For instance, video playback will be denied from `xyz.foo.example.com` when you include `["*.example.com"]`.

### Playback Restriction considerations

Here are some things to consider when using Playback Restrictions.

* You can create up to 100 different Playback Restrictions per environment on your Mux account.

* You can use a Playback Restriction ID for playing a single video or a group of videos.

* You have a lot of flexibility for associating Playback Restrictions with videos. For instance, you can create one Playback Restriction for each of your clients if your service or application supports multiple clients.

* You can add up to 100 different domains to each Playback Restriction.

* You can restrict playing video on domains added to the Playback Restriction. For instance, if you want multiple partner sites to play a video, you can add the partner site domain to the same Playback Restriction, thereby restricting playback only on those domains.

* If your player supports Chromecast, like Mux Player, make sure you add the Chromecast domain (`www.gstatic.com`) to your playback restrictions, otherwise casting will fail.

* if your player supports AirPlay, like Mux Player, you will only be able to AirPlay to third party devices by adding the AirPlay domain (`mediaservices.cdn-apple.com`) to your playback restrictions. Because first-party Apple devices never forward the referrer header, `allow_no_referrer` must be set to true in order to work on those devices, otherwise airplaying will fail.

[Reach out to Mux Support](mailto:support@mux.com) if you have a use case that requires more than 100 Playback Restrictions or want to add more than 100 domains per Playback Restriction.

### Using `Referer` HTTP Header for validation

Web browsers send the website address requesting the video in the [`Referer` HTTP header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referer).
Mux matches the domains configured in the Playback Restriction, with the domain in the `Referer` HTTP header. No video is delivered if there is no match.

The `Referer` HTTP header is only sent by web browsers, while native iOS and Android applications do not send this header. Therefore, Mux cannot perform domain validations on any requests from native iOS and Android applications. For this reason, you can configure the Playback Restrictions to allow or deny all HTTP requests without the `Referer` HTTP header by setting the `allow_no_referrer` boolean parameter.

First party Apple devices, like Apple TV 4K, never set a referrer header regardless of the source. Therefore if airplaying to first party Apple devices is required then `allow_no_referrer` will need to be set to `true` in order to succeed.

Please note that setting `allow_no_referrer` to `true` can result in content playback from unauthorised locations. As such, we strongly recommend creating two Playback Restriction objects, one with `allow_no_referrer` set to `true`, and one set to `false`, and setting the appropriate Playback Restriction ID in the JWT for Web vs. native iOS and/or Android applications.

### Using `User-Agent` HTTP Header for validation

The `User-Agent` HTTP header value is used to validate against a playback restriction. If the `allow_no_user_agent` field is set to false, the playback will be denied if the request does not include an `User-Agent` value.  For the `allow_high_risk_user_agent` validation, Mux maintains a list of user agents that have been known to be associated with higher risk video playback, such as playback devices that are not associated with legitimate end users of most systems. For more information, please reach out to [Mux Support](/support).

## 4. Generate a JSON Web Token (JWT)

All signed requests have a JWT with the following standard claims:

| Claim Code | Description | Value |
| :-- | :-- | :-- |
| sub | Subject of the JWT | Mux Video Playback ID |
| aud | Audience (intended application of the token) | `v` (Video or Subtitles/Closed Captions) <br /> `t` (Thumbnail) <br /> `g` (GIF) <br /> `s` (Storyboard) <br /> `d` (DRM License)|
| exp | Expiration time | UNIX Epoch seconds when the token expires. This should always exceed the current-time plus the duration of the video, else portions of the video may be unplayable. |
| kid | Key Identifier | Key ID returned when signing key was created |

You can also include the following optional claims depending on the type of request.

| Claim Code | Description | Value |
| :-- | :-- | :-- |
| playback\_restriction\_id | Playback Restriction Identifier | `PLAYBACK_RESTRICTION_ID` from the previous step. Mux performs validations when the `PLAYBACK_RESTRICTION_ID` is present to the JWT claims body. This claim is supported for all `aud` types. |

The Image (Thumbnails, Animated GIFs, Storyboard and others) API accepts several options to control image selection and transformations. More details on generating JWT for image can be found [here](/docs/guides/secure-video-playback#note-on-query-parameters-after-signing).

For Playback IDs that use a public policy, the thumbnail options are supplied as query parameters on the request URL.

For Playback IDs that use a signed policy, the thumbnail options must be specified in the JWT claims when using signed URLs. This ensures that the thumbnail options are not altered, such as changing the timestamp or the dimensions of the thumbnail image. For example, if you uploaded a 4K video and wanted to restrict a thumbnail to a width of 600 pixels and a specific timestamp, then simply include the `width` and `time` keys in the JWT claims.

## A note on expiration time

Expiration time should be at least the duration of the Asset or the expected duration of the Live Stream. When the signed URL expires, the URL will no longer be playable, even if playback has already started. Make sure you set the expiration to be sufficiently far in the future so that users do not experience an interruption in playback.

Your application should consider cases where the user loads a video, leaves your application, then comes back later and tries to play the video again. You will likely want to detect this behavior and make sure you fetch a new signed URL to make sure playback can start.

## 5. Sign the JSON Web Token (JWT)

The steps can be summarized as:

1. Load the private key used for signing
2. Assemble the claims (sub, exp, kid, aud, etc) in a map
3. Encode and sign the JWT using the claims map and private key and the RS256 algorithm.

There are dozens of software libraries for creating & reading JWTs. Whether you’re writing in Go, Elixir, Ruby, or a dozen other languages, don’t fret, there is most likely some JWT library you can rely on.

<Callout type="warning">
  The following examples assuming you're working with either a private key returned from the <ApiRefLink href="/docs/api-reference/system/signing-keys">Signing Keys API</ApiRefLink>, or copy & pasted from the Dashboard, **not** when downloaded as a PEM file.
</Callout>

```go

package main

import (
    "encoding/base64"
    "fmt"
    "log"
    "time"

    "github.com/golang-jwt/jwt/v4"
)

func main() {

    playbackId := "" // Enter your signed playback id here
    keyId      := "" // Enter your signing key id here
    key        := "" // Enter your base64 encoded private key here

    decodedKey, err := base64.StdEncoding.DecodeString(key)
    if err != nil {
        log.Fatalf("Could not base64 decode private key: %v", err)
    }

    signKey, err := jwt.ParseRSAPrivateKeyFromPEM(decodedKey)
    if err != nil {
        log.Fatalf("Could not parse RSA private key: %v", err)
    }

    token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
        "sub": playbackId,
        "aud": "v",
        "exp": time.Now().Add(time.Minute * 15).Unix(),
        "kid": keyId,
    })

    tokenString, err := token.SignedString(signKey)
    if err != nil {
        log.Fatalf("Could not generate token: %v", err)
    }

    fmt.Println(tokenString)
}

```

```node

// We've created some helper functions for Node to make your signing-life easier
const Mux = require('@mux/mux-node');
const mux = new Mux();

async function createTokens () {
  const playbackId = ''; // Enter your signed playback id here

  // Set some base options we can use for a few different signing types
  // Type can be either video, thumbnail, gif, or storyboard
  let baseOptions = {
    keyId: '', // Enter your signing key id here
    keySecret: '', // Enter your base64 encoded private key here
    expiration: '7d', // E.g 60, "2 days", "10h", "7d", numeric value interpreted as seconds
  };

  const token = await mux.jwt.signPlaybackId(playbackId, { ...baseOptions, type: 'video' });
  console.log('video token', token);

  // Now the signed playback url should look like this:
  // https://stream.mux.com/${playbackId}.m3u8?token=${token}

  // If you wanted to pass in params for something like a gif, use the
  // params key in the options object
  const gifToken = await mux.jwt.signPlaybackId(playbackId, {
    ...baseOptions,
    type: 'gif',
    params: { time: '10' },
  });
  console.log('gif token', gifToken);

  // Then, use this token in a URL like this:
  // https://image.mux.com/${playbackId}/animated.gif?token=${gifToken}

  // A final example, if you wanted to sign a thumbnail url with a playback restriction
  const thumbnailToken = await mux.jwt.signPlaybackId(playbackId, {
    ...baseOptions,
    type: 'thumbnail',
    params: { playback_restriction_id: YOUR_PLAYBACK_RESTRICTION_ID },
  });
  console.log('thumbnail token', thumbnailToken);

  // When used in a URL, it should look like this:
  // https://image.mux.com/${playbackId}/thumbnail.png?token=${thumbnailToken}
}

```

```php

<?php
  // Using Composer and https://github.com/firebase/php-jwt
  require __DIR__ . '/vendor/autoload.php';
  use \Firebase\JWT\JWT;

  $playbackId = ""; // Enter your signed playback id here
  $keyId = "";      // Enter your signing key id here
  $keySecret = "";  // Enter your base64 encoded private key here

  $payload = array(
    "sub" => $playbackId,
    "aud" => "t",          // v = video, t = thumbnail, g = gif.
    "exp" => time() + 600, // Expiry time in epoch - in this case now + 10 mins
    "kid" => $keyId,

    // Optional, include any additional manipulations
    "time"     => 10,
    "width"    => 640,
    "fit_mode" => "smartcrop"
  );

  $jwt = JWT::encode($payload, base64_decode($keySecret), 'RS256');

  print "$jwt\n";

?>

```

```python

# This example uses pyjwt / cryptography:
# pip install pyjwt
# pip install cryptography

import jwt
import base64
import time

playback_id = ''        # Enter your signed playback id here
signing_key_id = ''     # Enter your signing key id here
private_key_base64 = '' # Enter your base64 encoded private key here

private_key = base64.b64decode(private_key_base64)

token = {
    'sub': playback_id,
    'exp': int(time.time()) + 3600, # 1 hour
    'aud': 'v'
}
headers = {
    'kid': signing_key_id
}

json_web_token = jwt.encode(
    token, private_key, algorithm="RS256", headers=headers)

print(json_web_token)

```

```ruby

require 'base64'
require 'jwt'

def sign_url(playback_id, audience, expires, signing_key_id, private_key, params = {})
    rsa_private = OpenSSL::PKey::RSA.new(Base64.decode64(private_key))
    payload = {sub: playback_id, exp: expires.to_i, kid: signing_key_id, aud: audience}
    payload.merge!(params)
    JWT.encode(payload, rsa_private, 'RS256')
end

playback_id = ''        # Enter your signed playback id here
signing_key_id = ''     # Enter your signing key id here
private_key_base64 = '' # Enter your base64 encoded private key here

token = sign_url(playback_id, 'v', Time.now + 3600, signing_key_id, private_key_base64)

```



## 6. Include the JSON Web Token (JWT) in the media URL

Supply the JWT in the resource URL using the `token` query parameter. The Mux Video service will inspect and validate the JWT to make sure the request is allowed.

Video URL example:

```sh
https://stream.mux.com/{playback-id}.m3u8?token={JWT}
```

Thumbnail options are supplied as query parameters when using a public policy. When using a signed policy, the thumbnail options must be specified as claims in the JWT following the same naming conventions as with query parameters.

Thumbnail URL example:

```sh
https://image.mux.com/{playback-id}/thumbnail.{format}?token={JWT}
```

<Callout type="warning" title="Passing `token` for public playback IDs will fail">
  If you include a `token=` query parameter for a `"public"` playback ID, the URL will fail. This is intentional as to not create the false appearance of security when using a public playback ID.

  If your application uses a mix of "public" and "signed" playback IDs, you should save the playback policy type in your database and include the token parameter only for the signed playbacks.
</Callout>

## Note on query parameters after signing

When you're signing a URL, you're signing the parameters for that URL as well. After the parameters are signed for a playback ID, the resulting signed URL should *only* contain the `token` parameter. This is important because leaving the parameters in the URL would both:

* expose more information about the underlying asset than you may want
* result in an incorrect signature since the extraneous parameters would alter the URL.

<Callout type="warning" title="Be sure to include `params` in your `claims` body">
  While the JWT helper we expose in our Node SDK passes in additional parameters as an extra hash, when working with the JWT directly, these `params` should be embedded directly in your `claims` body.
</Callout>

## Example:

Let's say we're taking the following public example and making a signed URL:

* `https://image.mux.com/{public_playback_id}/thumbnail.jpg?time=25`

Generate a signed URL with `{time: 25}` in the **claims body**. Using the helper example we wrote above, this would look like:

* `sign(signedPlaybackId, { ...requiredTokenOptions, params: { time: 25 } })`

**Correct** Signed URL:

* `https://image.mux.com/{signed_playback_id}/thumbnail.jpg?token={token}`

**Bad** Signed URL:

* `https://image.mux.com/{signed_playback_id}/thumbnail.jpg?time=25&token={token}`

Including query parameters in the token also applies to playback modifiers like `default_subtitles_lang`, `redundant_streams` and `roku_trick_play`. The JWT claims body must include the extra parameter:

```json
{
  "sub": "{PLAYBACK_ID}",
  "aud": "{AUDIENCE_TYPE}",
  "exp": "{EXPIRATION_TIME}",
  "redundant_streams": true
}
```

## Passing custom parameters to a signed token

With signed URLs, you can pass extra parameters via a `custom` key in the claims body like the example above.

This may be useful in order to identify bad actors that share signed URLs in an unauthorized way outside of your application. If you find out that a signed URL gets shared then you can decode the parameters and trace it back to the user who shared it. When including extra parameters like this, be sure to respect the following guidelines:

* Do NOT under any circumstances include personally identifiable information (PII) like a name or email address.
* Put your custom parameters nested inside the `"custom"` key.

```json
{
  "sub": "{PLAYBACK_ID}",
  "aud": "{AUDIENCE_TYPE}",
  "exp": "{EXPIRATION_TIME}",
  "custom": {
    "session_id": "xxxx-123"
  }
}
```
