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>
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();
}
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
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
});
});
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
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=...
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");
}
Your server could then produce:
const expires = Math.floor(Date.now() / 1000) + 300;
const signature = createSignature(
video.id,
expires,
process.env.VIDEO_SECRET
);
The playback service verifies:
- the video ID
- the expiration timestamp
- 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";
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
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
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
});
});
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
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
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 |
+----------------+
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
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 │
└──────────────┘
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)