When building an application with Strapi, you may need to deliver private video content that is accessible only to specific users, such as subscribers or members. Simply hiding video links on the frontend is not secure; users could still access videos if file URLs are public. To ensure proper protection, Strapi provides role-based permissions, custom policies, and storage security mechanisms to keep your videos safe.
Prerequisites
- Node.js v18.x (recommended for Strapi v4)
- npm or Yarn (package manager)
- Basic Understanding of Strapi CLI
- Set Up a Strapi Project using this guide: Getting Started with Strapi: A Beginner"s Guide
Install and Configure Video Field Plugin
To enable video embedding, install the video field plugin.
Install the Plugin:
With npm:
npm install @sklinet/strapi-plugin-video-fieldWith yarn:
yarn add @sklinet/strapi-plugin-video-fieldEnable the plugin in config/plugins.js:
module.exports = () => ({
"video-field": {
enabled: true
}
});
Run build:
npm run buildOR
yarn buildOnce installed, you"ll see Video Field in the Content-Type Builder. A sample stored value looks like this:
{
"provider": "youtube",
"providerUid": "RANDOMUID",
"url": "https://www.youtube.com/watch?v=RANDOMUID"
}Secure Embedded Video Sources
If you plan to embed videos from providers such as YouTube or Vimeo, extend the Content Security Policy (CSP) in config/middlewares.js:
module.exports = [
"strapi::errors",
{
name: "strapi::security",
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
"frame-src": [
"'self'",
"youtube.com",
"www.youtube.com",
"vimeo.com",
"*.vimeo.com",
"facebook.com",
"www.facebook.com"
],
"media-src": ["'self'", "data:", "blob:"],
},
},
},
},
"strapi::cors",
"strapi::poweredBy",
"strapi::logger",
"strapi::query",
"strapi::body",
"strapi::session",
"strapi::favicon",
"strapi::public",
];This ensures that only trusted video providers can be embedded in your application.
Configure User Roles and Permissions
Strapi v4 has two types of roles:
Admin Roles: Control access to the Strapi admin panel.
Users & Permissions Plugin Roles: Control access for application users (public, authenticated, custom roles).
For private video content, use the Users & Permissions Plugin Roles.
Steps to Configure:
Step 1: In the Strapi admin panel, go to Settings > Users & Permissions Plugin > Roles.
Step 2: Create a custom role (e.g., Subscriber).
Step 3: Assign permissions for your video content types (Find, FindOne, Create, Update, Delete).
Step 4: Save changes.
Step 5: Assign this role to front-end users either via registration flow or manually in the → Users → section.
Implement Custom Logic for Granular Access
Basic role permissions may not be enough. For example, you may want users to access only the videos they own. Policies let you enforce such rules.
Example Policy: src/policies/isOwner.js
module.exports = async (ctx, config, { strapi }) => {
const { user } = ctx.state; // authenticated user
const { id } = ctx.params; // video ID from URL
// Replace 'video' with the name of your content-type
const video = await strapi.db.query('api::video.video').findOne({
where: { id },
});
if (!video || video.owner !== user.id) {
return false; // Access denied
}
return true; // Access granted
};Apply the policy in your routes file (src/api/video/routes/video.js):
module.exports = {
routes: [
{
method: "GET",
path: "/videos/:id",
handler: "video.findOne",
config: {
policies: ["isOwner"],
},
},
],
};This ensures that only the owner of a video can retrieve it.
Note: In the query above, api::video.video refers to a collection type called → video. → Update this identifier to match your actual content type name.
Securing Video Storage
Permissions and policies protect the API layer, but securing where the actual video files are stored is equally important.
If using Local Uploads, files stored in Strapi"s /public/uploads folder are publicly accessible if requested directly. Instead, move sensitive video files outside the /public directory and serve them through a secured Strapi route with authentication checks.
If using Cloud Storage (e.g., AWS S3), store files as private in the bucket. Generate signed URLs for users to access files temporarily after authentication.
Example: AWS S3 Signed URL Service
// src/services/s3.js
const AWS = require("aws-sdk");
const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECRET_KEY,
region: process.env.AWS_REGION,
});
module.exports = {
getSignedUrl(key) {
return s3.getSignedUrl("getObject", {
Bucket: process.env.AWS_BUCKET,
Key: key,
Expires: 60, // expires in 60 seconds
});
},
};Example Controller that uses the signed URL:
// src/api/video/controllers/video.js
const s3Service = require("../../../services/s3");
module.exports = {
async getSecureUrl(ctx) {
const { id } = ctx.params;
const { user } = ctx.state;
const video = await strapi.db.query('api::video.video').findOne({ where: { id } });
if (!video || video.owner !== user.id) {
return ctx.forbidden("You do not have access to this video.");
}
const url = s3Service.getSignedUrl(video.fileKey);
return { url };
}
};This ensures that even if someone shares a raw S3 file link, it won"t work, since only signed (expiring) URLs are served to authenticated users.

