Skip to content

Securing video playback with signed URLs

A video URL is just a link. Without any security, anyone who finds it can watch the content, share it, or embed it on their own site. That's fine for public content. But for subscription video, paid courses, internal training, or any content that should be gated behind your application — you need something more.

Signed URLs solve this problem by adding a cryptographic token to the playback link. The token contains claims about who can watch, from where, and for how long. Without a valid token, the video won't play.

This guide covers how signed URLs work, when to use them, and how to implement them in your video application using Mux.

LinkWhat signed URLs actually do

A signed URL is a playback link that includes a JWT (JSON Web Token) containing access rules that your server creates and Mux verifies. Instead of a simple URL like:

https://stream.mux.com/{PLAYBACK_ID}.m3u8

You get a URL with a token appended:

https://stream.mux.com/{PLAYBACK_ID}.m3u8?token={JWT}

The JWT is signed with your private key. When a viewer requests the video, Mux validates the signature, checks the claims (expiration, restrictions, etc.), and only serves the content if everything checks out.

This means:

  • Sharing the URL doesn't help—the token expires
  • Embedding on unauthorized sites fails—domain restrictions kick in
  • Downloading or recording the link for later won't work—time-based access controls

The important distinction: signed URLs protect access to content. They control who can request the video stream. For full content protection (preventing screen recording, for example), you'd combine signed URLs with DRM encryption.

LinkHow Mux handles signed video playback

Mux supports two playback policies: public and signed. Public playback IDs work without any restrictions—anyone with the URL can watch. Signed playback IDs require a valid JWT.

When you create an asset with a signed playback policy, the resulting playback ID will only work when accompanied by a properly signed token. Here's what that looks like in the API:

const Mux = require('@mux/mux-node'); const mux = new Mux(); // Create an asset with a signed playback policy const asset = await mux.video.assets.create({ input: [{ url: 'https://storage.example.com/video.mp4' }], playback_policy: ['signed'], }); const playbackId = asset.playback_ids[0].id; // This playback ID requires a JWT to work

Before you can sign tokens, you need to create a signing key in your Mux environment. This gives you a public/private key pair—you sign tokens with the private key, and Mux verifies them with the public key.

LinkCreating signed tokens with Mux

The Mux Node SDK includes helpers for generating JWTs. Here's a basic example:

const Mux = require('@mux/mux-node'); const mux = new Mux({ tokenId: process.env.MUX_TOKEN_ID, tokenSecret: process.env.MUX_TOKEN_SECRET, jwtSigningKey: process.env.MUX_SIGNING_KEY_ID, jwtPrivateKey: process.env.MUX_PRIVATE_KEY, }); // Sign a playback token const playbackToken = await mux.jwt.signPlaybackId(playbackId, { expiration: '24h', // Token valid for 24 hours }); // Construct the playback URL const playbackUrl = `https://stream.mux.com/${playbackId}.m3u8?token=${playbackToken}`;

The expiration parameter determines how long the token remains valid. Set this based on your use case:

  • Short expirations (minutes to hours): For live events, rental content, or highly sensitive material
  • Longer expirations (hours to days): For subscription content where users might return to continue watching
  • Very long expirations (7+ days): For content that doesn't change access frequently

One important note: when a signed URL expires, playback stops—even if the video is already playing. Set your expiration far enough in the future that viewers won't experience interruptions during normal viewing sessions.

LinkImplementing playback restrictions with Mux

Beyond expiration, Mux supports playback restrictions that let you control where videos can be played. The most common use case is domain restrictions.

First, create a playback restriction:

// Create a restriction that only allows playback on your domain const restriction = await mux.video.playbackRestrictions.create({ referrer: { allowed_domains: ['example.com', 'staging.example.com'], allow_no_referrer: false, // Block requests without a Referer header }, }); // Save this restriction ID for use in tokens const restrictionId = restriction.id;

Then reference the restriction when signing tokens:

const playbackToken = await mux.jwt.signPlaybackId(playbackId, { expiration: '24h', params: { playback_restriction_id: restrictionId, }, });

Now the video will only play when embedded on example.com or staging.example.com. Attempts to embed it elsewhere or access it directly will fail.

LinkHandling mobile apps

Native mobile apps don't send the Referer header that browsers use. If you're building iOS or Android apps, set allow_no_referrer: true in your playback restriction, or use separate restriction configurations for web versus mobile.

Mux also supports User-Agent restrictions, allowing you to filter out known high-risk user agents or require a specific User-Agent pattern for playback.

LinkSigned URLs vs DRM: when to use each

Signed URLs and DRM solve different problems, and for many applications, you need both.

Signed URLs protect access to the video stream. They prevent:

  • Unauthorized viewing (people who haven't paid or logged in)
  • Hotlinking and embedding on unauthorized sites
  • URL sharing that bypasses your authentication
  • Access after a rental or subscription period ends

DRM (Digital Rights Management) protects the content itself. It prevents:

  • Screen recording
  • Download and redistribution
  • Playback on unauthorized devices
  • Output capture via HDMI or other video outputs

For most video applications—courses, subscription content, paid webinars—signed URLs provide sufficient protection. DRM adds complexity (license servers, device compatibility, certification requirements) that may not be worth it unless you're protecting high-value content like movies or premium sports.

Mux supports both approaches. You can use signed URLs alone, or combine them with DRM (Widevine, FairPlay, PlayReady) for maximum protection.

LinkImplementing secure video in a Node backend

Here's a practical example of securing video playback in a Node.js application. This pattern works for Express, Fastify, or any Node-based backend:

import Mux from '@mux/mux-node'; import express from 'express'; const app = express(); const mux = new Mux({ tokenId: process.env.MUX_TOKEN_ID, tokenSecret: process.env.MUX_TOKEN_SECRET, jwtSigningKey: process.env.MUX_SIGNING_KEY_ID, jwtPrivateKey: process.env.MUX_PRIVATE_KEY, }); // Middleware to check if user has access to content async function checkVideoAccess(req, res, next) { const { videoId } = req.params; const { userId } = req.session; // Your business logic: does this user have access? const hasAccess = await checkUserVideoAccess(userId, videoId); if (!hasAccess) { return res.status(403).json({ error: 'Access denied' }); } next(); } // Endpoint that returns a signed playback URL app.get('/api/video/:videoId/playback', checkVideoAccess, async (req, res) => { const { videoId } = req.params; // Look up the Mux playback ID for this video const video = await getVideoFromDatabase(videoId); const playbackId = video.muxPlaybackId; // Generate a signed token const token = await mux.jwt.signPlaybackId(playbackId, { expiration: '4h', // Enough time to watch most content }); res.json({ playbackUrl: `https://stream.mux.com/${playbackId}.m3u8?token=${token}`, }); });

The key pattern here: your application handles authentication and authorization, then issues a signed token only to users who should have access. The token itself doesn't grant permanent access—it expires, and users must return to your application to get a new one.

LinkSecuring video in React applications

On the client side, never embed signing keys or generate tokens. Instead, fetch the signed URL from your backend:

import MuxPlayer from '@mux/mux-player-react'; import { useState, useEffect } from 'react'; function SecureVideoPlayer({ videoId }) { const [playbackUrl, setPlaybackUrl] = useState(null); const [error, setError] = useState(null); useEffect(() => { async function getSignedUrl() { try { const response = await fetch(`/api/video/${videoId}/playback`); if (!response.ok) { throw new Error('Access denied'); } const data = await response.json(); setPlaybackUrl(data.playbackUrl); } catch (err) { setError(err.message); } } getSignedUrl(); }, [videoId]); if (error) { return <div>Unable to load video: {error}</div>; } if (!playbackUrl) { return <div>Loading...</div>; } return ( <MuxPlayer src={playbackUrl} streamType="on-demand" metadata={{ video_title: 'Protected Video', viewer_user_id: 'user-123', }} /> ); }

This pattern ensures that token generation happens server-side, where your signing keys are protected. The client only receives the final signed URL.

LinkSecuring livestreams with signed tokens

Signed URLs work the same way for live streams as they do for on-demand content. Create a live stream with a signed playback policy:

const liveStream = await mux.video.liveStreams.create({ playback_policy: ['signed'], new_asset_settings: { playback_policy: ['signed'], // Recording will also be signed }, }); const playbackId = liveStream.playback_ids[0].id;

Then generate tokens for viewers the same way you would for VOD content. One consideration for live: set expiration times that cover the expected duration of the stream, plus some buffer. A 2-hour webinar might warrant a 4-hour token.

When the stream ends and the recording is created, it inherits the signed playback policy. Viewers will need new tokens to access the recording.

LinkAdding DRM for premium content protection

For content that needs protection beyond access control—preventing screen recording, blocking unauthorized capture—Mux offers DRM using Widevine, FairPlay, and PlayReady.

DRM requires additional setup (including a FairPlay certificate from Apple), but the integration follows a similar pattern. You generate two tokens: a playback token and a DRM license token.

// For DRM-protected content const playbackToken = await mux.jwt.signPlaybackId(playbackId, { expiration: '7d', }); const drmLicenseToken = await mux.jwt.signDrmLicense(playbackId, { expiration: '7d', }); // Use both tokens with Mux Player

In Mux Player, DRM is handled automatically when you provide both tokens:

<MuxPlayer playbackId={playbackId} tokens={{ playback: playbackToken, drm: drmLicenseToken, }} />

Mux Player detects the viewer's device capabilities and requests the appropriate DRM license (Widevine for Chrome/Android, FairPlay for Safari/iOS, PlayReady for Edge).

LinkBest practices for signed URL security

A few recommendations from working with customers who secure video at scale:

Keep signing keys secure. Store private keys in environment variables or a secrets manager, never in source code. Rotate keys periodically, especially if you suspect compromise.

Set appropriate expiration times. Too short, and users get interrupted during playback. Too long, and shared URLs remain valid longer than intended. Match expiration to your content and use case.

Use playback restrictions for domain locking. Even with short token expirations, domain restrictions add another layer that prevents embedding on unauthorized sites.

Handle token refresh gracefully. For long viewing sessions, your application should be able to generate fresh tokens before the current one expires. Check remaining validity and refresh proactively.

Combine with user authentication. Signed URLs work best when your application controls who can request them. Tie token generation to your existing auth system.

Monitor playback analytics. Mux Data shows you playback patterns, which can help identify unauthorized sharing or unusual access patterns.

LinkWhen signed URLs make sense

Signed URLs add complexity to your integration. They make sense when:

  • Paid content: Courses, subscription video, pay-per-view
  • Internal content: Training videos, corporate communications
  • Licensed content: Material you don't have rights to distribute openly
  • Live events: Webinars, concerts, conferences with paid admission
  • User-generated private content: Videos that uploaders want to keep private

For truly public content—marketing videos, product demos, anything meant to be shared—public playback IDs are simpler and work just as well.

LinkFAQ

LinkHow does token-based playback security work for gating subscriber-only tutorial videos?

Your application authenticates users, verifies their subscription status, then generates a short-lived JWT signed with your Mux signing key. The token is appended to the playback URL. Mux validates the token before serving video. Non-subscribers never receive a valid token, so they can't access the content even if they find the playback ID.

LinkWhat's the difference between signed URL tokens and DRM for protecting internal videos?

Signed URLs control access—who can request the video stream and for how long. DRM protects the content itself through encryption, preventing screen recording and unauthorized capture. For internal training videos, signed URLs are typically sufficient. DRM adds complexity that's usually reserved for high-value premium content.

LinkWhat's the simplest way to secure on-demand video playback with signed tokens in a Node backend?

Use the Mux Node SDK's JWT helpers. Configure your signing keys, then call mux.jwt.signPlaybackId(playbackId, { expiration: '24h' }) to generate a token. Return the signed URL from an authenticated API endpoint. Your frontend fetches this URL and passes it to the player.

Generate tokens server-side with longer expiration times and distribute them through your access-controlled pages. Viewers don't need accounts, but they do need to access the stream through your site where the token is embedded. Add domain restrictions to prevent the embed from working elsewhere.

LinkBest practices and APIs for securing on-demand product demo videos with signed URLs and watermarking?

For product demos that require tracking, use signed URLs with playback restrictions to control distribution. Add viewer-specific metadata to your Mux Data integration to track who watched what. Mux doesn't provide watermarking directly, but DRM combined with signed URLs prevents unauthorized redistribution.

LinkCan you walk me through best practices for securing VOD assets with signed tokens and DRM keys?

Start with signed playback policies on your assets. Generate playback tokens server-side with appropriate expiration (hours for rental, days for subscription). For premium content, add DRM and generate both playback and license tokens. Use playback restrictions to lock content to your domains. Monitor Mux Data for unusual access patterns.

LinkIn a Node + TypeScript project, how would you auto-convert uploaded MP4s to adaptive HLS for on-demand streaming?

Use Mux's upload API. When users upload MP4s to your application, create a Mux upload URL, direct the file there, and Mux automatically transcodes to adaptive HLS with multiple renditions. Add playback_policy: ['signed'] to require tokens for playback. The resulting playback ID returns an HLS manifest that works across all devices.

Arrow RightBack to Articles

No credit card required to start using Mux.