· Nacho Coll · Guides · 10 minit bacaan
Token Muat Naik IPFS: Muat Naik Sisi Klien yang Selamat Tanpa Mendedahkan API Key
Ketahui bagaimana signed upload token membolehkan anda memuat naik ke IPFS dengan selamat dari pelayar dan aplikasi mudah alih tanpa mendedahkan API key anda.

Membina aplikasi web moden sering memerlukan muat naik fail secara langsung dari pelayar pengguna ke storan awan. Walau bagaimanapun, dalam hal keselamatan muat naik IPFS, pembangun menghadapi dilema yang mencabar: bagaimana membenarkan muat naik sisi klien tanpa mendedahkan API key anda yang berharga kepada potensi penyalahgunaan?
Kebanyakan perkhidmatan pinning IPFS memaksa anda membuat pilihan yang tidak selesa: mengendalikan semua muat naik di sisi pelayan (mewujudkan kesesakan dan kerumitan) atau membenamkan API key anda dalam kod klien (mimpi ngeri keselamatan). IPFS.NINJA menyelesaikan masalah ini dengan ciri unik yang tiada perkhidmatan pinning lain tawarkan: signed upload token.

Masalah dengan API Key IPFS Tradisional
Apabila membina aplikasi sisi klien yang perlu memuat naik ke IPFS, pembangun biasanya menghadapi beberapa cabaran keselamatan:
Risiko Pendedahan API Key
Membenamkan API key secara langsung dalam JavaScript pelayar bermakna sesiapa sahaja boleh melihat kod sumber anda dan mengekstrak kelayakan anda. Ini boleh menyebabkan:
- Muat naik tanpa kebenaran yang menghabiskan kuota storan anda
- Potensi penyalahgunaan akaun perkhidmatan pinning anda
- Pelanggaran pematuhan keselamatan dalam persekitaran enterprise
Kesesakan Sisi Pelayan
Alternatifnya—mengalirkan semua muat naik melalui backend anda—mewujudkan beberapa isu:
- Peningkatan kos lebar jalur pelayan
- Kependaman yang lebih tinggi untuk pengguna
- Keperluan infrastruktur yang lebih kompleks
- Potensi titik kegagalan tunggal
Keselamatan Aplikasi Mudah Alih
Aplikasi mudah alih menghadapi cabaran yang sama, di mana API key yang disimpan dalam bundle aplikasi boleh diekstrak melalui kejuruteraan terbalik.
Memperkenalkan Token Muat Naik IPFS
Signed upload token IPFS.NINJA menyediakan jalan tengah yang selamat. Begini cara ia berfungsi:
- Pelayan menjana token: Backend anda mencipta token bertandatangan dengan had masa menggunakan API key anda
- Klien menerima token: Token dihantar dengan selamat ke aplikasi frontend anda
- Muat naik langsung: Klien memuat naik terus ke IPFS.NINJA menggunakan signed token
- Tamat tempoh automatik: Token tamat tempoh selepas tempoh yang ditetapkan, mengehadkan tetingkap pendedahan
Pendekatan ini menggabungkan keselamatan pengesahan sisi pelayan dengan manfaat prestasi muat naik langsung dari klien.
Memahami Keselamatan Upload Token
Signed upload token menggunakan tandatangan kriptografi untuk memastikan kesahihan tanpa mendedahkan API key utama anda. Setiap token mengandungi:
- Cap masa tamat tempoh: Pembatalan automatik selepas tempoh yang ditentukan
- Kekangan penggunaan: Had pilihan pada bilangan fail atau saiz keseluruhan
- Tandatangan kriptografi: Menghalang pengubahsuaian atau pemalsuan
- Pengesahan penerbit: Menghubungkan kembali ke akaun anda yang disahkan
Tidak seperti API key, upload token direka untuk dibenamkan dengan selamat dalam kod sisi klien. Walaupun diekstrak, ia menyediakan akses terhad yang tamat tempoh secara automatik.
Pelaksanaan Backend: Contoh Express.js
Mari kita bina contoh lengkap yang menunjukkan cara melaksanakan muat naik IPFS sisi klien yang selamat. Pertama, berikut backend Express.js yang menjana 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');
});Pelaksanaan Frontend: Muat Naik Klien yang Selamat
Sekarang, berikut kod frontend yang memuat naik fail dengan selamat 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 Keselamatan Lanjutan
Apabila melaksanakan keselamatan muat naik IPFS dengan signed token, pertimbangkan langkah-langkah keselamatan tambahan ini:
Had Skop Token
Konfigurasikan token dengan sekatan 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
})
});Pengesahan Kandungan
Sentiasa sahkan kandungan yang dimuat naik pada 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' });
}
});Pengehadan Kadar
Laksanakan pengehadan kadar tambahan pada endpoint penjanaan 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);Kelebihan Berbanding Pendekatan Tradisional
Signed upload token menyediakan beberapa kelebihan berbanding kaedah keselamatan muat naik IPFS alternatif:
vs. Proksi Sisi Pelayan
- Prestasi: Muat naik langsung menghapuskan penggunaan lebar jalur pelayan
- Kebolehskalaan: Tiada kesesakan pelayan semasa tempoh muat naik yang tinggi
- Kos: Pengurangan kos lebar jalur dan pemprosesan
- Pengalaman Pengguna: Kelajuan muat naik dan penjejakan kemajuan yang lebih baik
vs. API Key Sisi Klien
- Keselamatan: Tiada risiko pengekstrakan atau penyalahgunaan API key
- Pematuhan: Memenuhi keperluan audit keselamatan
- Kawalan Akses: Kebenaran terperinci dan tamat tempoh automatik
- Pemantauan: Penjejakan sumber dan corak muat naik yang lebih baik
vs. Perkhidmatan Pinning Lain
IPFS.NINJA pada masa ini merupakan satu-satunya perkhidmatan pinning utama yang menawarkan signed upload token. Pesaing seperti Pinata memerlukan sama ada proksi sisi pelayan atau pendedahan API key sisi klien, menjadikan ini pembeza yang unik.
Untuk butiran lanjut tentang perbandingan IPFS.NINJA dengan perkhidmatan lain, lihat panduan perbandingan komprehensif kami.
Petua Penerapan Pengeluaran
Apabila menerapkan signed upload token dalam pengeluaran:
Konfigurasi Persekitaran
Simpan konfigurasi sensitif dengan selamat:
// 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 Pengelogan
Laksanakan pengelogan komprehensif untuk pemantauan keselamatan:
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
});Pengendalian Ralat
Laksanakan pengendalian ralat yang kukuh dan tidak membocorkan maklumat 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 Rangka Kerja Popular
Signed upload token berfungsi dengan lancar bersama rangka kerja web moden. Berikut contoh integrasi pantas:
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 menyelesaikan cabaran keselamatan kritikal dalam pembangunan aplikasi terdesentralisasi. Dengan menyediakan cara selamat untuk membolehkan muat naik langsung sisi klien ke IPFS tanpa mendedahkan API key, ia membuka kemungkinan seni bina baharu untuk aplikasi web moden.
Sama ada anda membina sistem pengurusan kandungan, pasaran NFT, atau sebarang aplikasi yang memerlukan muat naik fail yang selamat, upload token IPFS.NINJA menyediakan keselamatan dan fleksibiliti yang anda perlukan. Pelaksanaannya mudah, manfaat keselamatannya signifikan, dan peningkatan prestasinya besar.
Untuk mengetahui lebih lanjut tentang asas IPFS, lihat panduan kami tentang apa itu IPFS pinning atau terokai tutorial API lengkap kami. Untuk pembangun yang menilai pelbagai pilihan, perbandingan perkhidmatan pinning IPFS terbaik kami menyediakan pandangan komprehensif.
Bersedia untuk melaksanakan muat naik IPFS sisi klien yang selamat dalam aplikasi anda? Gabungan amalan keselamatan moden dan storan terdesentralisasi menjadikan pendekatan ini ideal untuk aplikasi pengeluaran yang perlu berskala sambil mengekalkan standard keselamatan.
Bersedia untuk mula pinning? Cipta akaun percuma — 500 fail, storan 1 GB, gateway khusus. Tiada kad kredit diperlukan.
