A production-grade Cloudflare Worker that acts as a secure image CDN proxy with HMAC authentication and edge caching.
The Sendai CDN is available at https://cdn.sendai.fun and can be integrated into any application that needs secure, authenticated image proxying.
class SendaiCDN {
constructor(secretKey) {
this.secretKey = secretKey;
this.baseUrl = 'https://cdn.sendai.fun';
}
async generateSignedUrl(imageUrl, expiryMinutes = 60) {
const crypto = require('crypto');
const exp = Math.floor(Date.now() / 1000) + (expiryMinutes * 60);
const message = imageUrl + exp;
const signature = crypto.createHmac('sha256', this.secretKey).update(message).digest('hex');
const params = new URLSearchParams({
url: imageUrl,
sig: signature,
exp: exp.toString()
});
return `${this.baseUrl}/cdn?${params.toString()}`;
}
}
// Usage example
const cdn = new SendaiCDN('your-secret-key');
const proxiedUrl = await cdn.generateSignedUrl('https://example.com/image.jpg', 30);const crypto = require('crypto');
function generateCDNUrl(imageUrl, secretKey, expiryMinutes = 60) {
const exp = Math.floor(Date.now() / 1000) + (expiryMinutes * 60);
const message = imageUrl + exp;
const signature = crypto.createHmac('sha256', secretKey).update(message).digest('hex');
const params = new URLSearchParams({
url: imageUrl,
sig: signature,
exp: exp.toString()
});
return `https://cdn.sendai.fun/cdn?${params.toString()}`;
}
// Usage in Express.js API
app.get('/api/images/:id', (req, res) => {
const originalImageUrl = `https://storage.example.com/images/${req.params.id}`;
const proxiedUrl = generateCDNUrl(originalImageUrl, process.env.HMAC_SECRET);
res.json({ imageUrl: proxiedUrl });
});import { useState, useEffect } from 'react';
const SecureImage = ({ src, alt, secretKey, ...props }) => {
const [proxiedSrc, setProxiedSrc] = useState('');
useEffect(() => {
const generateProxiedUrl = async () => {
const exp = Math.floor(Date.now() / 1000) + 3600; // 1 hour
const message = src + exp;
const signature = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(secretKey),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
).then(key =>
crypto.subtle.sign('HMAC', key, new TextEncoder().encode(message))
).then(signature =>
Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('')
);
const params = new URLSearchParams({
url: src,
sig: signature,
exp: exp.toString()
});
setProxiedSrc(`https://cdn.sendai.fun/cdn?${params.toString()}`);
};
if (src) {
generateProxiedUrl();
}
}, [src, secretKey]);
return proxiedSrc ? <img src={proxiedSrc} alt={alt} {...props} /> : null;
};import hmac
import hashlib
import time
from urllib.parse import urlencode
def generate_cdn_url(image_url, secret_key, expiry_minutes=60):
exp = int(time.time()) + (expiry_minutes * 60)
message = f"{image_url}{exp}"
signature = hmac.new(
secret_key.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
params = {
'url': image_url,
'sig': signature,
'exp': str(exp)
}
return f"https://cdn.sendai.fun/cdn?{urlencode(params)}"- Keep your secret key secure - Store it in environment variables, never in client-side code
- Set appropriate expiry times - Balance security with caching efficiency
- Use HTTPS only - The CDN only accepts HTTPS image URLs
- Handle errors gracefully - Implement fallbacks for failed CDN requests
- Cache signed URLs - Generate URLs server-side and cache them to reduce computation
GET /cdn?url=<encoded_url>&sig=<hmac_signature>&exp=<unix_timestamp>
url: Base64 or URL-encoded HTTPS image URLsig: HMAC-SHA256 signature ofurl + expusing your secret keyexp: Unix timestamp (seconds) when the link expires
# Generate signature (Node.js example)
const crypto = require('crypto');
const url = 'https://example.com/image.jpg';
const exp = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now
const message = url + exp;
const signature = crypto.createHmac('sha256', 'your-secret-key').update(message).digest('hex');
# Request URL
https://your-worker.example.workers.dev/cdn?url=https%3A%2F%2Fexample.com%2Fimage.jpg&sig=${signature}&exp=${exp}- Configure your
HMAC_SECRETenvironment variable in Cloudflare Workers dashboard - Deploy using Wrangler:
wrangler publish- All image URLs must use HTTPS
- Signatures expire based on the
expparameter - Invalid signatures return 403 Forbidden
- Only image content-types are cached and served
- Images are cached at Cloudflare's edge for 1 year (immutable)
- Cache key is based on the original image URL
- Cache misses fetch from origin and store in edge cache
- Cache hits are served directly from edge