DEV Community

Cover image for Secure Video Hosting: Practical Ways to Protect Video Content in a Web Application
Faheem Zia
Faheem Zia

Posted on

Secure Video Hosting: Practical Ways to Protect Video Content in a Web Application

Adding video to a web application is easy.

Protecting that video is a different problem.

A basic implementation might look like this:

<video controls>
  <source src="https://example.com/videos/course-01.mp4" type="video/mp4">
</video>
Enter fullscreen mode Exit fullscreen mode

For public content, this may be perfectly acceptable.

But what happens when the video belongs to:

  • a paid course
  • a private community
  • an internal company portal
  • a subscription application
  • a client dashboard
  • a members-only website

In these cases, simply hiding the video URL in your frontend isn't real access control.

Let's look at a more practical architecture for handling private video.

The First Rule: Don't Trust the Frontend

Suppose your application checks whether a user is logged in:

if (user.isLoggedIn) {
  showVideo();
}
Enter fullscreen mode Exit fullscreen mode

This controls what appears in the UI, but it doesn't necessarily protect the underlying media.

If the actual video URL is publicly accessible, someone who obtains that URL may be able to request it directly.

The authorization decision should therefore happen on infrastructure you control.

Conceptually:

User
  |
  v
Application
  |
  +---- Authentication
  |
  +---- Authorization
  |
  v
Playback Permission
  |
  v
Video Delivery
Enter fullscreen mode Exit fullscreen mode

The player should be the last part of the process, not the security layer.

Authentication and Authorization Are Different

Authentication answers:

Who is this user?

Authorization answers:

Is this user allowed to watch this video?

That distinction matters.

A user may be successfully logged into your application but still not have permission to access every video.

For example:

app.get("/api/videos/:id/play", async (req, res) => {
  if (!req.user) {
    return res.status(401).json({
      error: "Authentication required"
    });
  }

  const video = await findVideo(req.params.id);

  if (!video) {
    return res.status(404).json({
      error: "Video not found"
    });
  }

  const allowed = await canWatch(req.user, video);

  if (!allowed) {
    return res.status(403).json({
      error: "Access denied"
    });
  }

  return res.json({
    playback_url: video.playback_url
  });
});
Enter fullscreen mode Exit fullscreen mode

The exact implementation depends on your stack, but the principle is universal: verify permission before providing access to protected content.

Avoid Permanent Public URLs for Private Content

Consider a URL like:

https://cdn.example.com/private-course/lesson-12.mp4
Enter fullscreen mode Exit fullscreen mode

If that URL works forever without any authorization requirement, your application has limited control once it is shared.

For sensitive content, a better approach can be temporary playback authorization.

For example:

https://video.example.com/play/abc123
    ?expires=1780000000
    &signature=...
Enter fullscreen mode Exit fullscreen mode

The URL becomes invalid after a defined period.

This doesn't make copying video impossible, but it reduces uncontrolled reuse of permanent media URLs.

Signed URLs

A common pattern is generating a signature on the backend.

A simplified example:

import crypto from "crypto";

function createSignature(videoId, expires, secret) {
  return crypto
    .createHmac("sha256", secret)
    .update(`${videoId}:${expires}`)
    .digest("hex");
}
Enter fullscreen mode Exit fullscreen mode

Your server could then produce:

const expires = Math.floor(Date.now() / 1000) + 300;

const signature = createSignature(
  video.id,
  expires,
  process.env.VIDEO_SECRET
);
Enter fullscreen mode Exit fullscreen mode

The playback service verifies:

  1. the video ID
  2. the expiration timestamp
  3. the signature

before serving protected content.

In a production system, use the signing mechanism recommended by your storage, CDN, or video provider instead of inventing a custom cryptographic protocol.

Don't Put Secrets in JavaScript

This sounds obvious, but it is worth repeating.

Never do this:

const VIDEO_SECRET = "my-super-secret-key";
Enter fullscreen mode Exit fullscreen mode

Anything shipped to the browser should be treated as visible to the user.

Secrets belong on the server:

Browser
   |
   | video ID
   v
Backend
   |
   | secret/signing logic
   v
Authorized playback URL
   |
   v
Browser
Enter fullscreen mode Exit fullscreen mode

Your frontend requests access.

Your backend decides whether to grant it.

Separate Video Infrastructure From Your App

You can store and serve video yourself, use object storage and a CDN, or use dedicated video infrastructure.

The important architectural decision is keeping your application responsible for business logic while allowing specialized infrastructure to handle large media delivery.

For developers who don't want to build the entire storage and streaming pipeline themselves, a secure video hosting service such as FileMoon is one possible approach to evaluate.

Whether you self-host or use a platform, the same questions still matter:

  • Who can request playback?
  • How long should access remain valid?
  • Can media URLs be reused?
  • Where is authorization enforced?
  • What happens when a user's subscription expires?

Those questions are more important than simply hiding the player controls.

Protect the API Too

Securing the media while leaving your API exposed can create another problem.

For example, avoid endpoints that reveal private playback information without checking authorization:

GET /api/videos/123
Enter fullscreen mode Exit fullscreen mode

If that endpoint returns a private playback URL to anyone, your player-level restrictions don't accomplish much.

Apply authorization consistently.

app.get("/api/videos/:id", authenticate, async (req, res) => {
  const video = await findVideo(req.params.id);

  if (!video) {
    return res.sendStatus(404);
  }

  if (!(await canWatch(req.user, video))) {
    return res.sendStatus(403);
  }

  res.json({
    id: video.id,
    title: video.title
  });
});
Enter fullscreen mode Exit fullscreen mode

Only return sensitive playback information after access has been approved.

Rate Limiting Can Help

Video-related APIs can also benefit from rate limiting.

For example, an endpoint that creates temporary playback tokens shouldn't allow unlimited requests.

Conceptually:

User -> Playback API -> Authorization -> Temporary Access
              |
              +-> Rate Limit
Enter fullscreen mode Exit fullscreen mode

Rate limiting isn't a replacement for authentication, but it adds another useful control against automated abuse.

Consider Domain Restrictions

If your videos are intended to be embedded only on your website, domain or referrer restrictions may provide another layer of control where supported.

For example:

Allowed:
https://app.example.com

Not expected:
https://random-site.example
Enter fullscreen mode Exit fullscreen mode

This isn't sufficient as the only security mechanism because request headers can sometimes be manipulated.

Think of it as an additional layer rather than your primary authorization system.

Security Should Be Layered

There usually isn't one magic feature that makes online video "secure."

A better model is layered security:

             User
               |
               v
       +----------------+
       | Authentication |
       +----------------+
               |
               v
       +----------------+
       | Authorization  |
       +----------------+
               |
               v
       +----------------+
       | Temporary URL  |
       +----------------+
               |
               v
       +----------------+
       | Video Delivery |
       +----------------+
Enter fullscreen mode Exit fullscreen mode

Depending on your application, additional layers might include:

  • signed URLs
  • expiring tokens
  • rate limiting
  • domain restrictions
  • session validation
  • logging
  • anomaly detection

The right combination depends on the value and sensitivity of your content.

Monitor Access Logs

Security isn't only about preventing requests.

Visibility matters too.

Useful events to log include:

user_id
video_id
timestamp
IP address
authorization result
token creation
playback request
Enter fullscreen mode Exit fullscreen mode

These logs can help identify patterns such as:

  • unusually high playback activity
  • repeated failed authorization
  • one account being used from many locations
  • excessive token generation
  • unexpected access to premium videos

Be deliberate about retention and privacy when collecting this data.

No Browser-Based Video Is Impossible to Capture

This is an important limitation.

If a legitimate user can watch a video, the video must ultimately be rendered on their device.

There is no simple HTML or JavaScript trick that makes browser-delivered media impossible to capture.

Disabling right-click, hiding controls, or obfuscating a URL should therefore not be treated as strong security.

The realistic goal is to:

  • prevent unauthorized access
  • make casual URL sharing less useful
  • limit how long access remains valid
  • detect suspicious activity
  • enforce application permissions

That's a much more useful security model.

A Practical Architecture

For a subscription application, you might end up with something like:

                 ┌──────────────┐
                 │     User     │
                 └──────┬───────┘
                        │
                        v
                 ┌──────────────┐
                 │ Application  │
                 └──────┬───────┘
                        │
                  Authentication
                        │
                        v
                 ┌──────────────┐
                 │ Authorization│
                 └──────┬───────┘
                        │
                  Playback Access
                        │
                        v
                 ┌──────────────┐
                 │ Video Layer  │
                 └──────────────┘
Enter fullscreen mode Exit fullscreen mode

Your application decides who can watch.

Your video layer handles how the media is delivered.

Keeping those responsibilities separate makes the system easier to reason about and easier to scale.

Final Thoughts

Secure video delivery isn't achieved by hiding an MP4 URL or disabling a browser feature.

It starts with proper authentication and authorization.

From there, temporary playback URLs, signed requests, rate limiting, access logs, and specialized video infrastructure can add additional layers depending on your requirements.

Most importantly, design video access as part of your application's security model from the beginning.

It's much easier to build access control into the architecture than to bolt it onto a large public video library later.

Top comments (0)