Private Assets
JWT-signed URLs for paid content and premium video access.
How It Works
text
1. Mark asset as private (is_private: true)
2. When user requests access, your backend generates a signed URL
3. Signed URL contains: asset_id, expiry time, optional IP lock
4. MediaKit validates the signature before serving content
5. Expired or tampered URLs return 403 ForbiddenMark Asset as Private
bash
curl -X PATCH /api/assets/1 \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"is_private": true}'Generate Signed URL
bash
curl -X POST /api/mediakit/signed-url \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{
"asset_id": 1,
"expires_in": 3600,
"ip": "203.0.113.50"
}'Response
{
"data": {
"url": "https://mediakitapi.gritcms.com/api/mediakit/private/1?token=eyJhbGci...",
"expires_at": "2026-03-29T13:00:00Z"
}
}Parameters
| Field | Type | Description |
|---|---|---|
| asset_id | number | Asset to access |
| expires_in | number | Seconds until expiry (default: 3600) |
| ip | string | Optional IP lock (only this IP can use the URL) |
Use Case: Paid Courses
api/lesson/[id].ts
// Your backend checks if user has purchased the course
const hasPurchased = await checkPurchase(userId, courseId);
if (!hasPurchased) return res.status(403).json({ error: 'Not purchased' });
// Generate a time-limited signed URL
const { data } = await fetch('/api/mediakit/signed-url', {
method: 'POST',
headers: {
'Authorization': `Bearer ${serviceToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
asset_id: lesson.videoAssetId,
expires_in: 7200, // 2 hours
ip: req.headers['x-forwarded-for'],
}),
}).then(r => r.json());
// Return signed URL to client
return res.json({ video_url: data.url });Configuration
Set the signing secret in your .env:
env
SIGNED_URL_SECRET=your-32-character-hex-secretGenerate with: openssl rand -hex 32