CDN & File Upload
Oblien CDN manages file uploads with secure, short-lived tokens. You generate tokens on your backend, then use them on your frontend to upload files directly to the CDN.
Overview
How It Works:
- Backend - Generate CDN token using your Client ID/Secret (server-side only)
- Frontend - Use token to upload files directly to CDN at
https://cdn.oblien.com/api - Oblien - Processes images, generates variants, and manages storage
- Backend - List, manage, and track user's uploaded files
What We Manage:
- Token generation and validation (1-minute expiration)
- File processing and image optimization
- Automatic variant generation (thumbnails, blur, etc.)
- File storage and CDN delivery
- Upload history and file tracking
What You Do:
- Generate tokens on your backend (one API call)
- Upload files from frontend using tokens
- (Optional) Manage and list user's files
Security: Never expose your Client ID or Client Secret in frontend code. Token generation must always happen on your backend server. Only the CDN token should be sent to the frontend.
Quick Start
1. Backend: Generate Token
// backend/api/cdn-token.js
import { OblienClient, OblienCDN } from 'oblien';
const client = new OblienClient({
clientId: process.env.OBLIEN_CLIENT_ID,
clientSecret: process.env.OBLIEN_CLIENT_SECRET
});
const cdn = new OblienCDN(client);
// Generate user token (upload/process permissions)
const tokenData = await cdn.generateUserToken();
// Send to frontend
res.json({
token: tokenData.token,
expiresIn: '1m' // Tokens expire in 1 minute
});const response = await fetch('https://api.oblien.com/cdn/token', {
method: 'POST',
headers: {
'X-Client-ID': process.env.OBLIEN_CLIENT_ID,
'X-Client-Secret': process.env.OBLIEN_CLIENT_SECRET
}
});
const { token } = await response.json();curl -X POST https://api.oblien.com/cdn/token \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Response:
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"scope": "user",
"permissions": ["upload", "process"],
"expiresIn": "1m"
}2. Frontend: Upload Files
// frontend/upload.js
async function uploadFile(file) {
// Get token from your backend
const { token } = await fetch('/api/cdn-token').then(r => r.json());
// Upload to CDN
const formData = new FormData();
formData.append('file', file);
const response = await fetch('https://cdn.oblien.com/api/', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData
});
const result = await response.json();
console.log('Uploaded:', result.url);
return result;
}import { useState } from 'react';
function FileUpload() {
const [uploading, setUploading] = useState(false);
const [url, setUrl] = useState(null);
const handleUpload = async (e) => {
const file = e.target.files[0];
if (!file) return;
setUploading(true);
try {
// Get token from your backend
const { token } = await fetch('/api/cdn-token').then(r => r.json());
// Upload to CDN
const formData = new FormData();
formData.append('file', file);
const response = await fetch('https://cdn.oblien.com/api/', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: formData
});
const result = await response.json();
setUrl(result.url);
} finally {
setUploading(false);
}
};
return (
<div>
<input type="file" onChange={handleUpload} disabled={uploading} />
{url && <img src={url} alt="Uploaded" />}
</div>
);
}# First, get token from your backend
TOKEN=$(curl -X POST http://your-backend.com/api/cdn-token | jq -r '.token')
# Then upload to CDN
curl -X POST https://cdn.oblien.com/api/ \
-H "Authorization: Bearer $TOKEN" \
-F "file=@image.jpg"Response:
{
"success": true,
"url": "https://cdn.oblien.com/abc123/image.jpg",
"filename": "1234567890-abc123-image.jpg",
"size": 102400,
"mime": "image/jpeg",
"variants": [
{
"variant": "thumb",
"url": "https://cdn.oblien.com/abc123/thumb/image.jpg"
},
{
"variant": "medium",
"url": "https://cdn.oblien.com/abc123/medium/image.jpg"
},
{
"variant": "blur",
"url": "https://cdn.oblien.com/abc123/blur/image.jpg"
}
]
}Token Generation
Generate User Token
User tokens have upload and process permissions. Perfect for standard file uploads.
const cdn = new OblienCDN(client);
const tokenData = await cdn.generateUserToken();
// Returns:
// {
// success: true,
// token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
// scope: "user",
// permissions: ["upload", "process"],
// expiresIn: "1m"
// }const response = await fetch('https://api.oblien.com/cdn/token', {
method: 'POST',
headers: {
'X-Client-ID': process.env.OBLIEN_CLIENT_ID,
'X-Client-Secret': process.env.OBLIEN_CLIENT_SECRET
}
});
const tokenData = await response.json();curl -X POST https://api.oblien.com/cdn/token \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"Generate Admin Token
Admin tokens have full permissions including read, list, info, and delete.
const tokenData = await cdn.generateAdminToken();
// Returns:
// {
// success: true,
// token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
// scope: "admin",
// permissions: ["upload", "process", "read", "list", "info", "delete"],
// expiresIn: "1m"
// }const response = await fetch('https://api.oblien.com/cdn/token/admin', {
method: 'POST',
headers: {
'X-Client-ID': process.env.OBLIEN_CLIENT_ID,
'X-Client-Secret': process.env.OBLIEN_CLIENT_SECRET
}
});
const tokenData = await response.json();curl -X POST https://api.oblien.com/cdn/token/admin \
-H "X-Client-ID: $OBLIEN_CLIENT_ID" \
-H "X-Client-Secret: $OBLIEN_CLIENT_SECRET"File Upload
Single File Upload
Upload a single file to the CDN.
// Get token from your backend first
const { token } = await fetch('/api/cdn-token').then(r => r.json());
// Upload file
const formData = new FormData();
formData.append('file', fileInput.files[0]);
const response = await fetch('https://cdn.oblien.com/api/', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData
});
const result = await response.json();
console.log(result.url); // https://cdn.oblien.com/abc123/image.jpg// Server-side: Upload directly (SDK handles tokens automatically)
import fs from 'fs';
const fileBuffer = fs.readFileSync('image.jpg');
const result = await cdn.upload(fileBuffer, {
filename: 'image.jpg'
});
console.log(result.url);# Get token from your backend API first
TOKEN=$(curl -X POST http://your-backend.com/api/cdn-token | jq -r '.token')
# Upload file to CDN
curl -X POST https://cdn.oblien.com/api/ \
-H "Authorization: Bearer $TOKEN" \
-F "file=@image.jpg"Multiple Files Upload
Upload multiple files at once.
const { token } = await fetch('/api/cdn-token').then(r => r.json());
const formData = new FormData();
Array.from(fileInput.files).forEach(file => {
formData.append('files', file);
});
const response = await fetch('https://cdn.oblien.com/api/multiple', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData
});
const result = await response.json();
console.log(result.files); // Array of uploaded filesconst files = [
fs.readFileSync('image1.jpg'),
fs.readFileSync('image2.jpg')
];
const result = await cdn.uploadMultiple(files, {
filenames: ['image1.jpg', 'image2.jpg']
});
console.log(result.files);TOKEN=$(curl -X POST http://your-backend.com/api/cdn-token | jq -r '.token')
curl -X POST https://cdn.oblien.com/api/multiple \
-H "Authorization: Bearer $TOKEN" \
-F "files=@image1.jpg" \
-F "files=@image2.jpg"Upload from URLs
Have the CDN download and process images from external URLs.
const { token } = await fetch('/api/cdn-token').then(r => r.json());
const response = await fetch('https://cdn.oblien.com/api/process-urls', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
urls: [
'https://example.com/image1.jpg',
'https://example.com/image2.png'
]
})
});
const result = await response.json();
console.log(result.files);const result = await cdn.uploadFromUrls([
'https://example.com/image1.jpg',
'https://example.com/image2.png'
]);
console.log(result.files);TOKEN=$(curl -X POST http://your-backend.com/api/cdn-token | jq -r '.token')
curl -X POST https://cdn.oblien.com/api/process-urls \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://example.com/image1.jpg",
"https://example.com/image2.png"
]
}'File Management
Manage and track user's uploaded files using session authentication.
List Files
Get a paginated list of uploaded files.
const result = await cdn.listFiles({
page: 1,
limit: 50,
sortBy: 'created_at',
sortOrder: 'DESC'
});
console.log(result.data.files);
console.log(result.data.pagination);const response = await fetch('https://api.oblien.com/cdn/files?page=1&limit=50', {
headers: {
'Cookie': 'session=your-session-cookie'
}
});
const result = await response.json();curl https://api.oblien.com/cdn/files?page=1&limit=50 \
--cookie "session=your-session-cookie"Response:
{
"success": true,
"data": {
"files": [
{
"id": 123,
"filename": "1234567890-abc123-image.jpg",
"original_filename": "image.jpg",
"cdn_url": "https://cdn.oblien.com/abc123/image.jpg",
"size": 102400,
"mime_type": "image/jpeg",
"variants": [...],
"upload_type": "upload",
"is_deleted": false,
"created_at": "2024-01-01T00:00:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 150,
"totalPages": 3,
"hasMore": true
}
}
}Get Storage Stats
Get user's storage usage statistics.
const stats = await cdn.getStats();
console.log(stats.data);
// {
// totalFiles: 150,
// totalSize: 157286400,
// activeFiles: 145,
// activeSize: 152166400,
// uploadedFiles: 100,
// urlFiles: 50
// }const response = await fetch('https://api.oblien.com/cdn/stats', {
headers: {
'Cookie': 'session=your-session-cookie'
}
});
const stats = await response.json();curl https://api.oblien.com/cdn/stats \
--cookie "session=your-session-cookie"Get File by ID
Get details of a specific file.
const file = await cdn.getFile(123);
console.log(file.data);const response = await fetch('https://api.oblien.com/cdn/files/123', {
headers: {
'Cookie': 'session=your-session-cookie'
}
});
const file = await response.json();curl https://api.oblien.com/cdn/files/123 \
--cookie "session=your-session-cookie"Delete File
Soft delete a file (mark as deleted, can be restored).
const result = await cdn.deleteFile(123);
console.log(result.message); // "File marked as deleted"const response = await fetch('https://api.oblien.com/cdn/files/123', {
method: 'DELETE',
headers: {
'Cookie': 'session=your-session-cookie'
}
});
const result = await response.json();curl -X DELETE https://api.oblien.com/cdn/files/123 \
--cookie "session=your-session-cookie"Restore File
Restore a soft-deleted file.
const result = await cdn.restoreFile(123);
console.log(result.message); // "File restored successfully"const response = await fetch('https://api.oblien.com/cdn/files/123/restore', {
method: 'POST',
headers: {
'Cookie': 'session=your-session-cookie'
}
});
const result = await response.json();curl -X POST https://api.oblien.com/cdn/files/123/restore \
--cookie "session=your-session-cookie"Image Variants
Oblien CDN automatically generates optimized variants of uploaded images:
- thumb - 150x150px thumbnail
- medium - 800x600px medium size
- blur - 75x75px blurred placeholder
- full - Original full-size image
All variants are available immediately after upload.
Complete Example
Here's a complete example showing backend token generation and frontend upload:
Backend (Node.js/Express)
// backend/routes/cdn.js
import { OblienClient, OblienCDN } from 'oblien';
import express from 'express';
const router = express.Router();
const client = new OblienClient({
clientId: process.env.OBLIEN_CLIENT_ID,
clientSecret: process.env.OBLIEN_CLIENT_SECRET
});
const cdn = new OblienCDN(client);
// Generate CDN token
router.post('/cdn-token', async (req, res) => {
try {
const tokenData = await cdn.generateUserToken();
res.json({ token: tokenData.token });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// List user's files
router.get('/my-files', async (req, res) => {
try {
const files = await cdn.listFiles({
page: parseInt(req.query.page) || 1,
limit: 50
});
res.json(files);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
export default router;Frontend (React)
// frontend/components/FileUploader.jsx
import { useState } from 'react';
function FileUploader() {
const [uploading, setUploading] = useState(false);
const [uploadedFile, setUploadedFile] = useState(null);
const [error, setError] = useState(null);
const handleUpload = async (e) => {
const file = e.target.files[0];
if (!file) return;
setUploading(true);
setError(null);
try {
// Step 1: Get token from your backend
const { token } = await fetch('/api/cdn-token', {
method: 'POST'
}).then(r => r.json());
// Step 2: Upload to CDN
const formData = new FormData();
formData.append('file', file);
const response = await fetch('https://cdn.oblien.com/api/', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`
},
body: formData
});
if (!response.ok) {
throw new Error('Upload failed');
}
const result = await response.json();
setUploadedFile(result);
} catch (err) {
setError(err.message);
} finally {
setUploading(false);
}
};
return (
<div>
<input
type="file"
onChange={handleUpload}
disabled={uploading}
accept="image/*"
/>
{uploading && <p>Uploading...</p>}
{error && <p style={{ color: 'red' }}>{error}</p>}
{uploadedFile && (
<div>
<h3>Uploaded Successfully!</h3>
<img src={uploadedFile.url} alt="Uploaded" style={{ maxWidth: '400px' }} />
<p>URL: {uploadedFile.url}</p>
<p>Size: {(uploadedFile.size / 1024).toFixed(2)} KB</p>
<h4>Variants:</h4>
{uploadedFile.variants.map(v => (
<div key={v.variant}>
<strong>{v.variant}:</strong> {v.url}
</div>
))}
</div>
)}
</div>
);
}
export default FileUploader;API Reference
CDN Endpoints
Token Generation (Backend)
POST /cdn/token- Generate user tokenPOST /cdn/token/admin- Generate admin token
File Upload (Frontend)
POST https://cdn.oblien.com/api/- Upload single filePOST https://cdn.oblien.com/api/multiple- Upload multiple filesPOST https://cdn.oblien.com/api/process-urls- Upload from URLs
File Management (Backend)
GET /cdn/files- List filesGET /cdn/stats- Get storage statsGET /cdn/files/:id- Get file by IDDELETE /cdn/files/:id- Delete filePOST /cdn/files/:id/restore- Restore file
Token Scopes
- user -
upload,processpermissions - admin - All permissions including
read,list,info,delete
Security
Critical:
- Never expose Client ID/Secret in frontend code
- Always generate tokens on your backend
- Tokens expire in 1 minute for security
- File management requires session authentication
Best Practices
- Token Generation - Always generate tokens on your backend, never in frontend code
- Token Expiration - Tokens expire in 1 minute. Generate fresh tokens for each upload
- Error Handling - Handle token expiration and network errors gracefully
- File Size - Check file size limits before upload to provide better UX
- Progress Tracking - Show upload progress for better user experience
- Image Optimization - Use appropriate variant sizes for different use cases
Troubleshooting
Token Expired
Tokens expire in 1 minute. Generate a fresh token before each upload:
// ❌ Bad: Reusing old token
const { token } = await getTokenOnce();
await upload1(token); // Works
await upload2(token); // May fail if > 1 minute passed
// ✅ Good: Fresh token for each upload
await upload1(await getToken());
await upload2(await getToken());CORS Errors
The CDN endpoint https://cdn.oblien.com/api has CORS enabled. If you see CORS errors, check that you're using the correct endpoint and including the Authorization header.
File Size Limits
Different file types have different size limits. Use cdn.getLimits() to check current limits.