DEV Community

Brian Marsaw
Brian Marsaw

Posted on

Building SaaS for Authenticated Image and Video Hosting: A Complete Guide

Building SaaS for Authenticated Image and Video Hosting: A Complete Guide

Authenticated media hosting is a critical but often overlooked infrastructure need for modern applications. Whether you're building a healthcare portal with sensitive patient documents, a membership site with premium video content, or an enterprise dashboard with proprietary images, you need more than a CDN—you need controlled, secure, authenticated access to your media files.

This guide walks through the architectural decisions, security considerations, and practical implementation details for building a SaaS platform that solves this problem at scale.

Why Standard CDNs and Object Storage Aren't Enough

Services like Cloudflare R2, AWS S3, or Vercel Blob Storage are excellent for public assets. But when your media requires authentication, things get complicated quickly:

  • Signed URLs expire: Pre-signed URLs work for temporary access, but managing expiration times across sessions is cumbersome
  • No fine-grained permissions: You can't easily implement role-based access control (RBAC) or per-user quotas
  • Cookie sharing issues: Cross-domain cookies are increasingly restricted by browsers
  • Complex proxy logic: Rolling your own authentication proxy means maintaining infrastructure, handling caching, and optimizing delivery

A dedicated SaaS for authenticated media hosting abstracts these complexities while providing developer-friendly APIs and seamless integration.

Core Architecture Components

1. Authentication Layer

The foundation is a robust authentication system that works across domains. The best approach uses JWT tokens passed either as:

  • Authorization headers (best for API clients)
  • Secure, SameSite cookies (best for browser requests)
  • Query parameters (fallback for limited environments)

Here's a TypeScript implementation for generating access tokens:

typescript
import jwt from 'jsonwebtoken';

interface MediaAccessToken {
userId: string;
resourceId: string;
permissions: string[];
exp: number;
}

export function generateMediaToken(
userId: string,
resourceId: string,
permissions: string[] = ['read'],
expiresIn: string = '1h'
): string {
const payload: MediaAccessToken = {
userId,
resourceId,
permissions,
exp: Math.floor(Date.now() / 1000) + parseExpiry(expiresIn),
};

return jwt.sign(payload, process.env.JWT_SECRET!, {
algorithm: 'HS256',
});
}

// Usage in your API
app.get('/api/media/:id/token', async (req, res) => {
const { id } = req.params;
const userId = req.user.id; // from your auth middleware

// Check if user has access to this resource
const hasAccess = await checkUserAccess(userId, id);

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

const token = generateMediaToken(userId, id, ['read']);

res.json({
token,
url: https://media.yourservice.com/${id}?token=${token}
});
});

2. Storage Backend with Metadata

Your storage layer should separate the actual media (stored in object storage like S3) from the metadata (stored in a fast database like PostgreSQL).

Key metadata to track:

  • Owner/tenant ID: For multi-tenant isolation
  • Access rules: JSON field for complex permission logic
  • Upload date and size: For analytics and quota enforcement
  • MIME type and dimensions: For serving optimized versions
  • Encryption status: Whether the file is encrypted at rest

python

Python example using SQLAlchemy

from sqlalchemy import Column, String, Integer, DateTime, JSON
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class MediaAsset(Base):
tablename = 'media_assets'

id = Column(String, primary_key=True)
tenant_id = Column(String, nullable=False, index=True)
owner_id = Column(String, nullable=False, index=True)
storage_key = Column(String, nullable=False)  # S3 key
mime_type = Column(String, nullable=False)
file_size = Column(Integer, nullable=False)
access_rules = Column(JSON, default={})
created_at = Column(DateTime, nullable=False)

# For video-specific metadata
duration = Column(Integer, nullable=True)
width = Column(Integer, nullable=True)
height = Column(Integer, nullable=True)
Enter fullscreen mode Exit fullscreen mode

3. Caching and Edge Delivery

Even with authentication, you want fast delivery. Implement a multi-tier caching strategy:

  1. Application-level cache (Redis): Cache token validation results (5-15 minutes)
  2. Edge cache (Cloudflare/Fastly): Cache actual media with custom cache keys that include auth tokens
  3. Browser cache: Set appropriate Cache-Control headers for authenticated requests

The trick is using Vary: Authorization or Vary: Cookie headers to ensure cached responses respect authentication state.

Implementing Fine-Grained Access Control

The real power comes from flexible access control. Your SaaS should support:

Role-Based Access (RBAC)

typescript
interface AccessRule {
type: 'public' | 'authenticated' | 'role' | 'specific_users';
roles?: string[];
userIds?: string[];
expiresAt?: Date;
}

function checkAccess(
user: User | null,
asset: MediaAsset,
rule: AccessRule
): boolean {
switch (rule.type) {
case 'public':
return true;

case 'authenticated':
  return user !== null;

case 'role':
  return user?.roles.some(r => rule.roles?.includes(r)) ?? false;

case 'specific_users':
  return rule.userIds?.includes(user?.id ?? '') ?? false;

default:
  return false;
Enter fullscreen mode Exit fullscreen mode

}
}

Time-Limited Access

For scenarios like paid courses or temporary file sharing, implement expiring access:

python
from datetime import datetime, timedelta

def grant_temporary_access(asset_id: str, user_id: str, duration_hours: int = 24):
"""Grant time-limited access to a media asset"""
access_grant = AccessGrant(
asset_id=asset_id,
user_id=user_id,
granted_at=datetime.utcnow(),
expires_at=datetime.utcnow() + timedelta(hours=duration_hours),
access_count=0,
max_accesses=None # unlimited during the time window
)
db.session.add(access_grant)
db.session.commit()

Handling Video-Specific Requirements

Video hosting introduces additional complexity:

Adaptive Bitrate Streaming: Generate HLS or DASH manifests with authentication tokens embedded in segment URLs. Each segment URL should have its own short-lived token.

Thumbnail Generation: Automatically extract thumbnails at upload time. Store multiple sizes (small, medium, large) for different use cases.

Transcoding Pipeline: Integrate with services like AWS MediaConvert or build your own with FFmpeg. Queue jobs asynchronously and notify clients when processing completes.

Bandwidth Quotas: Track bandwidth per tenant/user to prevent abuse and enable usage-based pricing.

Pricing and Monetization Strategy

Successful SaaS platforms in this space typically offer:

  • Storage tier: $0.10-0.25/GB/month
  • Bandwidth tier: $0.08-0.15/GB transferred
  • Request tier: $0.50-1.00 per 10,000 authenticated requests
  • Processing tier: Video transcoding at $0.01-0.03 per minute

Consider offering a generous free tier (5-10GB storage, 50GB bandwidth) to reduce friction for developers evaluating your service.

Developer Experience Matters

Your SaaS lives or dies by its developer experience. Provide:

  1. SDK libraries for Python, TypeScript, Ruby, Go
  2. React components for drop-in image/video players with authentication built-in
  3. Webhook integrations for upload completion, processing failures, quota alerts
  4. Comprehensive docs with runnable examples
  5. Terraform/CloudFormation templates for enterprise customers

Conclusion

Building a SaaS for authenticated image and video hosting solves a real infrastructure gap. The market is underserved, and developers are actively seeking alternatives to rolling their own solutions or duct-taping together generic storage with homegrown auth.

The key differentiators are: bulletproof security, zero-config cross-domain authentication, fine-grained access control, and excellent developer ergonomics. Start with a focused MVP—authenticated image delivery with JWT tokens—then expand into video, analytics, and advanced access patterns based on customer feedback.

The technical challenges are real, but the moat you build from operational excellence and developer trust is substantial. Focus on reliability, clear pricing, and making the first integration take under 10 minutes.

Top comments (0)