· Nacho Coll · Guides · 10 menit baca
Token Upload IPFS: Upload Sisi Klien yang Aman Tanpa Mengekspos API Key
Pelajari bagaimana signed upload token memungkinkan Anda mengunggah ke IPFS dengan aman dari browser dan aplikasi mobile tanpa mengekspos API key Anda.

Membangun aplikasi web modern sering kali memerlukan pengunggahan file langsung dari browser pengguna ke penyimpanan cloud. Namun, dalam hal keamanan upload IPFS, pengembang menghadapi dilema yang menantang: bagaimana cara mengizinkan upload sisi klien tanpa mengekspos API key Anda yang berharga terhadap potensi penyalahgunaan?
Sebagian besar layanan pinning IPFS memaksa Anda memilih pilihan yang tidak nyaman: menangani semua upload di sisi server (menciptakan bottleneck dan kompleksitas) atau menyematkan API key Anda di kode klien (mimpi buruk keamanan). IPFS.NINJA menyelesaikan masalah ini dengan fitur unik yang tidak ditawarkan layanan pinning lain: signed upload token.

Masalah dengan API Key IPFS Tradisional
Ketika membangun aplikasi sisi klien yang perlu mengunggah ke IPFS, pengembang biasanya menghadapi beberapa tantangan keamanan:
Risiko Tereksposnya API Key
Menyematkan API key langsung di JavaScript browser berarti siapa pun bisa melihat kode sumber Anda dan mengekstrak kredensial Anda. Ini bisa menyebabkan:
- Upload tidak sah yang menghabiskan kuota penyimpanan Anda
- Potensi penyalahgunaan akun layanan pinning Anda
- Pelanggaran kepatuhan keamanan di lingkungan enterprise
Bottleneck Sisi Server
Alternatifnya—mengalirkan semua upload melalui backend Anda—menciptakan beberapa masalah:
- Peningkatan biaya bandwidth server
- Latensi lebih tinggi bagi pengguna
- Persyaratan infrastruktur yang lebih kompleks
- Potensi titik kegagalan tunggal
Keamanan Aplikasi Mobile
Aplikasi mobile menghadapi tantangan serupa, di mana API key yang disimpan dalam bundle aplikasi dapat diekstrak melalui reverse engineering.
Memperkenalkan Token Upload IPFS
Signed upload token dari IPFS.NINJA menyediakan jalan tengah yang aman. Berikut cara kerjanya:
- Server menghasilkan token: Backend Anda membuat token bertanda tangan dengan batas waktu menggunakan API key Anda
- Klien menerima token: Token ditransmisikan dengan aman ke aplikasi frontend Anda
- Upload langsung: Klien mengunggah langsung ke IPFS.NINJA menggunakan signed token
- Kedaluwarsa otomatis: Token kedaluwarsa setelah durasi yang ditentukan, membatasi jendela paparan
Pendekatan ini menggabungkan keamanan autentikasi sisi server dengan manfaat kinerja upload langsung dari klien.
Memahami Keamanan Upload Token
Signed upload token menggunakan tanda tangan kriptografi untuk memastikan keaslian tanpa mengekspos API key utama Anda. Setiap token berisi:
- Timestamp kedaluwarsa: Invalidasi otomatis setelah durasi yang ditentukan
- Batasan penggunaan: Batas opsional pada jumlah file atau ukuran total
- Tanda tangan kriptografi: Mencegah pemalsuan atau peniruan
- Verifikasi penerbit: Terhubung kembali ke akun Anda yang terautentikasi
Tidak seperti API key, upload token dirancang agar aman disematkan di kode sisi klien. Bahkan jika diekstrak, token tersebut menyediakan akses terbatas yang kedaluwarsa secara otomatis.
Implementasi Backend: Contoh Express.js
Mari kita buat contoh lengkap yang menunjukkan cara mengimplementasikan upload IPFS sisi klien yang aman. Pertama, berikut backend Express.js yang menghasilkan upload token:
// server.js
const express = require('express');
const cors = require('cors');
const app = express();
app.use(express.json());
app.use(cors());
// Your IPFS.NINJA API key (keep this secure on server-side only)
const IPFS_API_KEY = 'bws_1234567890abcdef1234567890abcdef12345678';
// Generate a signed upload token
app.post('/api/generate-upload-token', async (req, res) => {
try {
const response = await fetch('https://api.ipfs.ninja/upload-tokens', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': IPFS_API_KEY
},
body: JSON.stringify({
expiresIn: '1h', // Token valid for 1 hour
maxUploads: 10, // Optional: limit number of uploads
maxSizeMB: 50 // Optional: limit total upload size
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const tokenData = await response.json();
res.json({
success: true,
uploadToken: tokenData.token,
expiresAt: tokenData.expiresAt
});
} catch (error) {
console.error('Token generation failed:', error);
res.status(500).json({
success: false,
error: 'Failed to generate upload token'
});
}
});
// Optional: Endpoint to verify uploads completed successfully
app.post('/api/verify-upload', async (req, res) => {
const { cid } = req.body;
try {
// Verify the file was pinned successfully
const response = await fetch(`https://api.ipfs.ninja/pins/${cid}`, {
headers: {
'X-Api-Key': IPFS_API_KEY
}
});
const pinData = await response.json();
res.json({
success: true,
verified: pinData.pinned,
metadata: pinData
});
} catch (error) {
res.status(500).json({
success: false,
error: 'Verification failed'
});
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});Implementasi Frontend: Upload Klien yang Aman
Sekarang, berikut kode frontend yang mengunggah file dengan aman menggunakan signed token:
<!DOCTYPE html>
<html>
<head>
<title>Secure IPFS Upload Demo</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
.upload-area { border: 2px dashed #ccc; padding: 20px; text-align: center; margin: 20px 0; }
.upload-area.dragover { border-color: #007cba; background: #f0f8ff; }
button { background: #007cba; color: white; border: none; padding: 10px 20px; cursor: pointer; }
.status { margin: 10px 0; padding: 10px; border-radius: 4px; }
.success { background: #d4edda; color: #155724; }
.error { background: #f8d7da; color: #721c24; }
.info { background: #d1ecf1; color: #0c5460; }
</style>
</head>
<body>
<h1>Secure IPFS Upload with Signed Tokens</h1>
<div class="upload-area" id="uploadArea">
<p>Drag & drop files here or click to select</p>
<input type="file" id="fileInput" multiple style="display: none;">
<button onclick="document.getElementById('fileInput').click()">Select Files</button>
</div>
<div id="status"></div>
<div id="results"></div>
<script>
class SecureIPFSUploader {
constructor() {
this.uploadToken = null;
this.tokenExpiry = null;
this.setupEventListeners();
}
setupEventListeners() {
const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput');
// Drag and drop handlers
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
this.handleFiles(Array.from(e.dataTransfer.files));
});
// File input handler
fileInput.addEventListener('change', (e) => {
this.handleFiles(Array.from(e.target.files));
});
}
async getUploadToken() {
// Check if we have a valid token
if (this.uploadToken && this.tokenExpiry && new Date() < new Date(this.tokenExpiry)) {
return this.uploadToken;
}
try {
this.showStatus('Generating secure upload token...', 'info');
const response = await fetch('/api/generate-upload-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`Failed to generate token: ${response.statusText}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.error || 'Token generation failed');
}
this.uploadToken = data.uploadToken;
this.tokenExpiry = data.expiresAt;
return this.uploadToken;
} catch (error) {
this.showStatus(`Token generation failed: ${error.message}`, 'error');
throw error;
}
}
async uploadFile(file) {
try {
const token = await this.getUploadToken();
// Convert file to base64 for JSON transport
const fileBase64 = await this.fileToBase64(file);
this.showStatus(`Uploading ${file.name} to IPFS...`, 'info');
const response = await fetch('https://api.ipfs.ninja/upload/new', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Signed ${token}`
},
body: JSON.stringify({
content: fileBase64,
description: `File uploaded via secure token: ${file.name}`,
metadata: {
filename: file.name,
fileType: file.type,
uploadedAt: new Date().toISOString(),
uploadMethod: 'signed-token'
}
})
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Upload failed: ${response.status} ${errorText}`);
}
const result = await response.json();
return {
success: true,
filename: file.name,
cid: result.cid,
size: result.sizeMB,
ipfsUri: result.uris.ipfs,
httpUrl: result.uris.url
};
} catch (error) {
return {
success: false,
filename: file.name,
error: error.message
};
}
}
async handleFiles(files) {
if (files.length === 0) return;
this.clearResults();
try {
// Upload files concurrently
const uploadPromises = files.map(file => this.uploadFile(file));
const results = await Promise.all(uploadPromises);
this.displayResults(results);
const successful = results.filter(r => r.success).length;
const total = results.length;
if (successful === total) {
this.showStatus(`✅ Successfully uploaded ${successful} file(s) to IPFS!`, 'success');
} else {
this.showStatus(`⚠️ Uploaded ${successful}/${total} files. Check results below.`, 'error');
}
} catch (error) {
this.showStatus(`Upload failed: ${error.message}`, 'error');
}
}
fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
// Remove the data:mime/type;base64, prefix
const base64 = reader.result.split(',')[1];
resolve(base64);
};
reader.onerror = error => reject(error);
});
}
showStatus(message, type) {
const statusDiv = document.getElementById('status');
statusDiv.className = `status ${type}`;
statusDiv.textContent = message;
}
displayResults(results) {
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = '<h3>Upload Results:</h3>' +
results.map(result => `
<div class="status ${result.success ? 'success' : 'error'}" style="margin: 10px 0;">
<strong>${result.filename}</strong><br>
${result.success ?
`✅ CID: ${result.cid}<br>
📊 Size: ${result.size} MB<br>
🔗 URL: <a href="${result.httpUrl}" target="_blank">${result.httpUrl}</a>` :
`❌ Error: ${result.error}`
}
</div>
`).join('');
}
clearResults() {
document.getElementById('results').innerHTML = '';
}
}
// Initialize the uploader
const uploader = new SecureIPFSUploader();
</script>
</body>
</html>Pertimbangan Keamanan Lanjutan
Ketika mengimplementasikan keamanan upload IPFS dengan signed token, pertimbangkan langkah-langkah keamanan tambahan berikut:
Pembatasan Cakupan Token
Konfigurasikan token dengan batasan yang sesuai:
// Generate token with specific constraints
const restrictedToken = await fetch('https://api.ipfs.ninja/upload-tokens', {
method: 'POST',
headers: {
'X-Api-Key': IPFS_API_KEY
},
body: JSON.stringify({
expiresIn: '30m', // Short expiration
maxUploads: 5, // Limited upload count
maxSizeMB: 10, // Size restriction
allowedMimeTypes: ['image/jpeg', 'image/png'], // File type restrictions
ipWhitelist: ['192.168.1.0/24'] // IP-based access control
})
});Validasi Konten
Selalu validasi konten yang diunggah di backend Anda:
app.post('/api/validate-upload', async (req, res) => {
const { cid } = req.body;
try {
// Fetch and validate the uploaded content
const response = await fetch(`https://ipfs.ninja/ipfs/${cid}`);
const contentType = response.headers.get('content-type');
// Implement your validation logic
if (!isValidContentType(contentType)) {
// Remove invalid content
await deleteFromIPFS(cid);
return res.status(400).json({ error: 'Invalid content type' });
}
res.json({ success: true, validated: true });
} catch (error) {
res.status(500).json({ error: 'Validation failed' });
}
});Pembatasan Laju (Rate Limiting)
Terapkan pembatasan laju tambahan pada endpoint pembuatan token Anda:
const rateLimit = require('express-rate-limit');
const tokenLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // Limit each IP to 10 token requests per windowMs
message: 'Too many token requests, please try again later'
});
app.use('/api/generate-upload-token', tokenLimiter);Keunggulan Dibanding Pendekatan Tradisional
Signed upload token memberikan beberapa keunggulan dibandingkan metode keamanan upload IPFS alternatif:
vs. Proxy Sisi Server
- Kinerja: Upload langsung menghilangkan penggunaan bandwidth server
- Skalabilitas: Tidak ada bottleneck server selama periode upload tinggi
- Biaya: Pengurangan biaya bandwidth dan pemrosesan
- Pengalaman Pengguna: Kecepatan upload dan pelacakan progres yang lebih baik
vs. API Key Sisi Klien
- Keamanan: Tidak ada risiko ekstraksi atau penyalahgunaan API key
- Kepatuhan: Memenuhi persyaratan audit keamanan
- Kontrol Akses: Izin yang detail dan kedaluwarsa otomatis
- Pemantauan: Pelacakan sumber dan pola upload yang lebih baik
vs. Layanan Pinning Lain
IPFS.NINJA saat ini merupakan satu-satunya layanan pinning utama yang menawarkan signed upload token. Pesaing seperti Pinata memerlukan proxy sisi server atau eksposur API key sisi klien, menjadikan ini pembeda yang unik.
Untuk detail lebih lanjut tentang perbandingan IPFS.NINJA dengan layanan lain, lihat panduan perbandingan komprehensif kami.
Tips Deployment Produksi
Saat men-deploy signed upload token di produksi:
Konfigurasi Lingkungan
Simpan konfigurasi sensitif dengan aman:
// Use environment variables for production
const config = {
ipfsApiKey: process.env.IPFS_API_KEY,
tokenExpiry: process.env.UPLOAD_TOKEN_EXPIRY || '1h',
maxFileSize: process.env.MAX_FILE_SIZE_MB || 50,
allowedOrigins: process.env.ALLOWED_ORIGINS?.split(',') || ['localhost:3000']
};Pemantauan dan Pencatatan Log
Terapkan pencatatan log yang komprehensif untuk pemantauan keamanan:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'upload-security.log' })
]
});
// Log token generation
logger.info('Upload token generated', {
userId: req.user.id,
clientIP: req.ip,
userAgent: req.get('User-Agent'),
expiresAt: tokenData.expiresAt
});Penanganan Error
Terapkan penanganan error yang kokoh dan tidak membocorkan informasi sensitif:
app.use((error, req, res, next) => {
// Log full error details server-side
logger.error('Upload token error', {
error: error.message,
stack: error.stack,
userId: req.user?.id,
endpoint: req.path
});
// Send safe error message to client
res.status(500).json({
success: false,
error: 'An internal error occurred. Please try again.'
});
});Integrasi dengan Framework Populer
Signed upload token bekerja dengan mulus di framework web modern. Berikut contoh integrasi cepat:
React Hook
import { useState, useCallback } from 'react';
export function useSecureIPFSUpload() {
const [uploading, setUploading] = useState(false);
const [uploadToken, setUploadToken] = useState(null);
const getToken = useCallback(async () => {
if (uploadToken?.expiresAt && new Date() < new Date(uploadToken.expiresAt)) {
return uploadToken.token;
}
const response = await fetch('/api/generate-upload-token', {
method: 'POST'
});
const data = await response.json();
setUploadToken(data);
return data.uploadToken;
}, [uploadToken]);
const uploadFile = useCallback(async (file) => {
setUploading(true);
try {
const token = await getToken();
// Upload logic here...
} finally {
setUploading(false);
}
}, [getToken]);
return { uploadFile, uploading };
}Vue.js Composable
import { ref } from 'vue';
export function useSecureUpload() {
const uploading = ref(false);
const uploadProgress = ref(0);
const uploadFile = async (file) => {
uploading.value = true;
// Implementation here...
};
return {
uploading: readonly(uploading),
uploadProgress: readonly(uploadProgress),
uploadFile
};
}Kesimpulan
Signed upload token memecahkan tantangan keamanan kritis dalam pengembangan aplikasi terdesentralisasi. Dengan menyediakan cara aman untuk mengaktifkan upload langsung sisi klien ke IPFS tanpa mengekspos API key, mereka membuka kemungkinan arsitektur baru untuk aplikasi web modern.
Baik Anda membangun sistem manajemen konten, marketplace NFT, atau aplikasi apa pun yang memerlukan upload file yang aman, upload token IPFS.NINJA menyediakan keamanan dan fleksibilitas yang Anda butuhkan. Implementasinya sederhana, manfaat keamanannya signifikan, dan peningkatan kinerjanya substansial.
Untuk mempelajari lebih lanjut tentang dasar-dasar IPFS, lihat panduan kami tentang apa itu IPFS pinning atau jelajahi tutorial API lengkap kami. Untuk pengembang yang mengevaluasi berbagai opsi, perbandingan layanan pinning IPFS terbaik kami menyediakan wawasan komprehensif.
Siap mengimplementasikan upload IPFS sisi klien yang aman di aplikasi Anda? Kombinasi praktik keamanan modern dan penyimpanan terdesentralisasi menjadikan pendekatan ini ideal untuk aplikasi produksi yang perlu berkembang sambil menjaga standar keamanan.
Siap mulai pinning? Buat akun gratis — 500 file, penyimpanan 1 GB, gateway khusus. Tidak perlu kartu kredit.
