- Introduction to Video
- Stream video files
- Start live streaming
- Make API requests
- Play your videos
- Enable static MP4 renditions
- Download for offline editing
- Embed videos for social media
- Listen for webhooks
- Secure video playback
- Create clips from your videos
- Get images from a video
- Create timeline hover previews
- Adjust audio levels
- Add watermarks to your videos
- Add subtitles to your videos
- Minimize processing time
- Upload files directly
- Autoplay your videos
- Stream an audio-only version of your video
- Synchronize video playback
- Integrate with your CMS
Secure video playback
In this guide you will learn how to use signed URLs for securing video playback.
In this guide:
1
Create an Asset or Live Stream with a "signed" playback policy
1
Create an Asset or Live Stream with a "signed" playback policy
2
Create the signing key for your Mux account environment
2
Create the signing key for your Mux account environment
3
Create the Playback Restriction for your Mux account environment
3
Create the Playback Restriction for your Mux account environment
4
Generate a JSON Web Token
4
Generate a JSON Web Token
5
Signing the JWT
5
Signing the JWT
6
Include the JWT in the media URL
6
Include the JWT in the media URL
Note on query parameters after signing
Note on query parameters after signing
You can create Playback IDs for assets and live streams. Playback IDs have two types - public
and signed
.
Public video playback URLs can be watched anywhere, any time, without any restrictions. Signed playback URLs, on the other hand,
include JSON Web Token (JWT) that are signed server-side by your application. There are two distinct ways to restrict video playback
using signed URLs:
- Expiration time: Viewers can watch videos until the expiration time. All requests past the expiration time are denied.
- Playback Restrictions: Playback Restrictions allow you to implement rules, like a list of allowed websites, for playing videos. Referrer Validation is the only supported Playback Restriction today; Mux plans to add more types of restrictions in the future.
An Asset or a Live Stream can have multiple playback IDs, each with a playback policy that can either be public
or signed
.
The playback policy can be specified when creating a new Asset, creating a new Live Stream, or can be added to an existing
asset and live stream.
//POST https://api.mux.com/video/assets { "input": "https://storage.googleapis.com/muxdemofiles/mux-video-intro.mp4", "playback_policy": "signed" }
See Create a playback IDAPI to learn how to add a new playback ID to an existing Asset or Live Stream.
Signing keys can be managed (created, deleted, listed) either from the dashboard or via the Mux Video API. When creating a new signing key, the API generates a 2048-bit RSA key-pair and returns the private key and a generated key-id. Securely store the private key for signing the token, and 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 only after any existing signed URLs have expired.
See Create a URL signing keyAPI for full documentation.
//POST https://api.mux.com/video/v1/signing-keys { "data": { "private_key": "(base64-encoded PEM file with private key)", "id": "(unique signing-key identifier)", "created_at": "(UNIX Epoch seconds)” } }
Mux supports one type of playback restriction - Referrer Validation.
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 Playback RestrictionAPI for full documentation.
API Request
//POST https://api.mux.com/video/v1/playback-restrictions { "referrer": { "allowed_domains" : [ "example.com" ], "allow_no_referrer" : false } }
API Response
{ "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.
There are a few additional details for you to consider when creating Playback Restrictions:
Add all the unique domains and sub-domains embedding your videos. For instance, add
example.com
,foo.example.com
andbar.example.com
when you want to authorize any of those web pages to play your videos. Alternatively, you could also add*.example.com
wildcard subdomain in addition toexample.com
to allow playback onexample.com
and all the subdomains ofexample.com
.The wildcard "*" only matches for one sub-domain level. For instance, video playback will be denied from xyz.foo.example.com when you set *.example.com.You can also let any websites to play your videos by just adding a single
*
wildcard entry to the domain list.
Using Referer
HTTP Header for validation
Web browsers send the website address requesting the video in the Referer
HTTP header.
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. You should only set the allow_no_referrer
parameter value to
true
if you are using a native iOS and/or Android applications.
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) t (Thumbnail) g (GIF) s (Storyboard) |
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.
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.
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 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 at some time in the future 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 is able to start.
The steps can be summarized as:
- Load the private key used for signing
- Assemble the claims (sub, exp, kid, aud, etc) in a map
- 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’s probably a JWT library that you can rely on.
The following examples assuming you're working with either a private key returned from the Signing Keys APIAPI, or copy & pasted from the Dashboard, not when downloaded as a PEM file.
package main
import (
"encoding/base64"
"fmt"
"log"
"time"
"github.com/dgrijalva/jwt-go"
)
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)
}
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:
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:
https://image.mux.com/{playback-id}/thumbnail.{format}?token={JWT}
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.
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.
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.
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:
{ "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.
{ "sub": "{PLAYBACK_ID}", "aud": "{AUDIENCE_TYPE}", "exp": "{EXPIRATION_TIME}", "custom": { "session_id": "xxxx-123" } }