Securing Video Content with Signed URLs

“Our video streams have been hacked.”

That was the rumour floating around the office of my past employer in February 2008. I was a software engineer working on the video encoding pipeline for a mobile video service. The RTSP URLs for our live streams had been posted on a popular web forum, allowing anyone to access our 40+ live video streams without a subscription.

Lawyers issued a take-down letter to the owner of the web forum. The forum owner’s response was: "I don't think I've done anything wrong … they're not really doing anything to protect their content provider's content." (www.theregister.co.uk)

Broken Lock

Our CEO changed course and had mobile- and server-engineers scramble to add expiring signed URLs to all live and on-demand videos. This secured the streams and helped restore the confidence of our content providers.

These days it’s often as simple as viewing the page source to find the HLS or DASH URL for a video.

“Hacking our video streams” might sound like a massive feat, but I assure you it was not. The stream URLs were probably found by running a packet-capture against the mobile device network traffic, possibly with some DNS spoofing. These days it’s often as simple as viewing the page source to find the HLS or DASH URL for a video.

An especially egregious aspect of leeching or hotlinking to content on other sites is that the content owner is responsible for paying the storage, bandwidth, and other costs while the linking site is able to use the content for free.

Signed URLs are an effective method of discouraging leeching and hotlinking.

alt

Mux Video Support for Signed URLs

We're excited to announce that signed URLs are available to all users of Mux Video! Signed URLs have been one of the most requested features at Mux. Our intuitive video APIs have been extended to include management of keys that can be used to digitally sign video & image URLs.

How It Works

A signed URL for a video or image includes a token that expires some time in the future. Requests beyond the token expiration time will return an HTTP error code, as will requests with an altered or missing token. Tokens are digitally signed using strong cryptography that ensures the token attributes are not tampered with.

Mux uses signed JSON Web Tokens (JWT) to describe access restrictions. JWT was defined by the open standard RFC 7519 to create a JSON format for sharing authorization claims between parties.

Reasons for choosing JWT include:

  • Large number of libraries to generate & validate JWTs in many different languages
  • JWT claim-sharing use-case was a perfect match for signed URLs
  • Lots of documentation on JWT best-practices helps ensure that we and our customers do the right thing

The JWT is a JSON document signed with a SHA-256 hash calculated using an RSA private key that can be verified by Mux with the complementary public key. Every video or thumbnail URL can have a unique signed token that can be used to authorize the request. Requests for descendent resources like rendition manifests and video segments will be signed with the same constraints specified in the initial JWT (e.g. expiration time).

Let’s walk through the steps to secure Mux content using signed URLs.

1. Create an Asset that uses the Signed Playback Policy

A Mux Video asset can have multiple playback-id’s, each with a playback policy that is public or signed. A playback-id that uses the public policy must not be accessed using a signed URL; a playback-id that uses the signed policy must be accessed with a signed URL.

The playback policy can be specified when creating a new asset, or you can use the playback policy API to create a new playback-id with the desired policy.

Request

POST https://api.mux.com/video/assets  
{
  "input": "https://storage.googleapis.com/muxdemofiles/mux-video-intro.mp4",
  "playback_policy": "signed"
}

2. Create a Signing Key

Signing keys can be managed (created, deleted, listed) via the Mux Video API. When creating a new signing key, the API will generate a 2048-bit RSA key-pair and return the private key and a generated key-id; the public key will be stored at Mux to validate signed tokens.

Signing keys are created and deleted independently of assets. This enables key rotation at intervals of your choosing. Though you probably only need one signing key active at a time, there is no limit to the number of signing keys you can create. A signing key can be used to sign JWTs for an unlimited number of assets. Store the private-key in a secure manner.

Request

POST https://api.mux.com/video/v1/signing-keys  

Response

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

3. Build a JSON Web Token

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

Claim Code Description Value
sub Subject of the JWT Mux Video Playback ID
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
aud Audience (intended application of token) v (Video) or t (Thumbnail)

Here's an example:

{
  "sub": "y3fkg2Bgl01CiSEL1umOxz8PmndRR01zYI",
  "exp": 1525126316,
  "kid": "xJbFkAfb6XkkESZAItIaZep8KOdi5N8e",
  "aud": "v"
}

The Thumbnail API accepts several options to control image selection and transformations.

For playback-id’s that use a public policy, the thumbnail options are supplied as query parameters on the request URL.

For playback-id’s 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.

4. Sign the 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

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.

Here’s a JWT signing example written in Ruby with the ruby-jwt library:

5. Include the JWT in the Media URL

Supply the JWT in the resource URL using the ‘token’ query parameter. The Mux Video service will enforce the inspect and validate the JWT to make sure the request is allowed. You’re all set!

Video

Policy URL Pattern
Public https://stream.mux.com/(playback-id).m3u8
Signed https://stream.mux.com/(playback-id).m3u8?token=(JWT)

Thumbnails

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.

Policy URL Pattern
Public https://image.mux.com/(playback-id)/thumbnail.(format)?(thumbnail options)
Signed https://image.mux.com/(playback-id)/thumbnail.(format)?token=(JWT)

Conclusion

It’s in your interest to protect where & how your videos and images are accessed. Your brand, creative work, bandwidth, and storage costs are at stake. Failure to do this can erode the confidence of your content creators and audience. Signed URLs are an effective and time-tested approach to securing content.

We encourage you to try the signed URLs feature in Mux Video. This capability was prioritized based on feedback from our amazing customers. We would love to hear your feedback on signed URLs or any other features that would improve your video delivery experience.

"Osaka castle outer wall and moat" by Serge is licensed under CC BY 2.0

"Broken Lock" by Ben Brown is licensed under CC BY 2.0

"Keys" by Alex Z. is licensed under CC BY 2.0