· Nacho Coll · Guides · 12 min basahin
Mga IPFS Upload Token: Ligtas na Client-Side Upload Nang Hindi Inilalantad ang API Key
Alamin kung paano pinapayagan ka ng mga signed upload token na mag-upload nang ligtas sa IPFS mula sa mga browser at mobile app nang hindi inilalantad ang iyong API key.

Ang pagbuo ng mga modernong web application ay madalas nangangailangan ng pag-upload ng mga file nang direkta mula sa mga browser ng mga gumagamit patungo sa cloud storage. Gayunpaman, pagdating sa seguridad ng IPFS upload, nahaharap ang mga developer sa isang mapanghamong dilema: paano payagan ang client-side upload nang hindi inilalantad ang iyong mahalagang API key sa posibleng pag-aabuso?
Karamihan sa mga IPFS pinning service ay pinipilit kang pumili sa isang hindi komportableng pagpipilian: pangasiwaan ang lahat ng upload sa server-side (na lumilikha ng mga bottleneck at pagiging kumplikado) o i-embed ang iyong API key sa client code (isang bangungot sa seguridad). Nilulutas ito ng IPFS.NINJA gamit ang isang natatanging feature na hindi inaalok ng ibang pinning service: mga signed upload token.

Ang Problema sa Tradisyunal na IPFS API Key
Kapag bumubuo ng client-side application na kailangang mag-upload sa IPFS, karaniwang nahaharap ang mga developer sa ilang hamon sa seguridad:
Panganib na Mailantad ang API Key
Ang pag-embed ng API key nang direkta sa browser JavaScript ay nangangahulugan na kahit sino ay makakatingin ng iyong source code at makakakuha ng iyong mga kredensyal. Ito ay maaaring humantong sa:
- Hindi awtorisadong upload na umuubos ng iyong storage quota
- Posibleng pag-aabuso ng iyong pinning service account
- Mga paglabag sa seguridad na sumusunod sa enterprise environment
Mga Bottleneck sa Server-Side
Ang alternatibo---pag-route ng lahat ng upload sa iyong backend---ay lumilikha ng ilang isyu:
- Tumaas na gastos sa server bandwidth
- Mas mataas na latency para sa mga gumagamit
- Mas kumplikadong mga kinakailangan sa infrastructure
- Posibleng single point of failure
Seguridad ng Mobile App
Ang mga mobile application ay nahaharap sa katulad na mga hamon, kung saan ang mga API key na nakaimbak sa mga app bundle ay maaaring makuha sa pamamagitan ng reverse engineering.
Ipinapakilala ang mga IPFS Upload Token
Ang mga signed upload token ng IPFS.NINJA ay nagbibigay ng ligtas na gitnang daan. Narito kung paano ito gumagana:
- Gumagawa ng token ang server: Ang iyong backend ay lumilikha ng time-limited, signed token gamit ang iyong API key
- Natatanggap ng client ang token: Ang token ay ligtas na ipinapadala sa iyong frontend application
- Direktang upload: Nag-a-upload nang direkta ang mga client sa IPFS.NINJA gamit ang signed token
- Awtomatikong pag-expire: Nag-e-expire ang mga token pagkatapos ng itinakdang tagal, nilalimitahan ang exposure window
Pinagsasama ng pamamaraang ito ang seguridad ng server-side authentication sa mga benepisyo sa performance ng direktang client upload.
Pag-unawa sa Seguridad ng Upload Token
Gumagamit ang mga signed upload token ng mga cryptographic signature para tiyakin ang pagiging tunay nang hindi inilalantad ang iyong pangunahing API key. Ang bawat token ay naglalaman ng:
- Expiration timestamp: Awtomatikong invalidation pagkatapos ng tinukoy na tagal
- Mga limitasyon sa paggamit: Opsyunal na mga limitasyon sa bilang ng file o kabuuang laki
- Cryptographic signature: Pinipigilan ang pag-tamper o pag-forge
- Issuer verification: Naka-link pabalik sa iyong authenticated account
Hindi tulad ng mga API key, ang mga upload token ay idinisenyo upang ligtas na mai-embed sa client-side code. Kahit na makuha, nagbibigay lamang sila ng limitadong access na awtomatikong nag-e-expire.
Backend Implementation: Halimbawa sa Express.js
Bumuo tayo ng kumpletong halimbawa na nagpapakita kung paano i-implement ang ligtas na client-side IPFS upload. Una, narito ang Express.js backend na gumagawa ng mga 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');
});Frontend Implementation: Ligtas na Client Upload
Ngayon, narito ang frontend code na ligtas na nag-a-upload ng mga file gamit ang 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>Mga Advanced na Konsiderasyon sa Seguridad
Kapag nagpapatupad ng IPFS upload security gamit ang mga signed token, isaalang-alang ang mga karagdagang hakbang sa seguridad na ito:
Mga Limitasyon sa Saklaw ng Token
I-configure ang mga token na may angkop na mga paghihigpit:
// 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
})
});Pag-validate ng Nilalaman
Palaging i-validate ang na-upload na nilalaman sa iyong backend:
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' });
}
});Rate Limiting
Magpatupad ng karagdagang rate limiting sa iyong token generation endpoint:
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);Mga Benepisyo Kumpara sa Tradisyunal na Pamamaraan
Ang mga signed upload token ay nagbibigay ng ilang kalamangan kumpara sa mga alternatibong paraan ng seguridad ng IPFS upload:
vs. Server-Side Proxying
- Performance: Ang direktang upload ay nag-aalis ng paggamit ng server bandwidth
- Scalability: Walang server bottleneck sa mga panahon ng mataas na upload
- Gastos: Mas mababang gastos sa bandwidth at processing
- Karanasan ng Gumagamit: Mas mabilis na upload speed at progress tracking
vs. Client-Side API Key
- Seguridad: Walang panganib ng pagkuha o pag-aabuso ng API key
- Pagsunod: Natutugunan ang mga kinakailangan sa security audit
- Access Control: Detalyadong mga pahintulot at awtomatikong pag-expire
- Pagsubaybay: Mas magandang pagsubaybay ng mga source at pattern ng upload
vs. Ibang Pinning Service
Ang IPFS.NINJA sa kasalukuyan ang tanging pangunahing pinning service na nag-aalok ng mga signed upload token. Ang mga kompetidor tulad ng Pinata ay nangangailangan ng server-side proxying o client-side API key exposure, na ginagawa itong isang natatanging pagkakaiba.
Para sa karagdagang detalye kung paano inihahambing ang IPFS.NINJA sa ibang mga serbisyo, tingnan ang aming komprehensibong gabay sa paghahambing.
Mga Tip sa Production Deployment
Kapag nagde-deploy ng mga signed upload token sa production:
Konfigurason ng Environment
Itago ang sensitibong konfigurason nang ligtas:
// 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']
};Pagsubaybay at Pag-log
Magpatupad ng komprehensibong pag-log para sa security monitoring:
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
});Pag-handle ng Error
Magpatupad ng matibay na error handling na hindi nagbubunyag ng sensitibong impormasyon:
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.'
});
});Integrasyon sa mga Sikat na Framework
Ang mga signed upload token ay gumagana nang maayos sa mga modernong web framework. Narito ang mga mabilis na halimbawa ng integrasyon:
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
};
}Konklusyon
Nilulutas ng mga signed upload token ang isang kritikal na hamon sa seguridad sa pagbuo ng mga decentralized application. Sa pamamagitan ng pagbibigay ng ligtas na paraan para payagan ang direktang client-side upload sa IPFS nang hindi inilalantad ang mga API key, nagbubukas sila ng mga bagong posibilidad sa arkitektura para sa mga modernong web application.
Nagtatayo ka man ng content management system, NFT marketplace, o anumang application na nangangailangan ng ligtas na file upload, ang mga upload token ng IPFS.NINJA ay nagbibigay ng seguridad at flexibility na kailangan mo. Ang pagpapatupad ay simple, ang mga benepisyo sa seguridad ay makabuluhan, at ang mga pakinabang sa performance ay malaki.
Para matuto pa tungkol sa mga batayan ng IPFS, tingnan ang aming gabay sa ano ang IPFS pinning o tuklasin ang aming kumpletong API tutorial. Para sa mga developer na nagsusuri ng iba’t ibang opsyon, ang aming pinakamahusay na IPFS pinning service na paghahambing ay nagbibigay ng komprehensibong mga insight.
Handa ka na bang magpatupad ng ligtas na client-side IPFS upload sa iyong application? Ang kombinasyon ng mga modernong kasanayan sa seguridad at decentralized storage ay ginagawang ideal ang pamamaraang ito para sa mga production application na kailangang mag-scale habang pinapanatili ang mga pamantayan sa seguridad.
Handa ka na bang mag-simula ng pinning? Gumawa ng libreng account --- 500 file, 1 GB storage, dedicated gateway. Hindi kailangan ng credit card.
