· Nacho Coll · Guides · 10 min čtení
Tokeny pro nahrávání na IPFS: bezpečné nahrávání na straně klienta bez odhalení API klíčů
Zjistěte, jak podepsané tokeny pro nahrávání umožňují bezpečně nahrávat soubory na IPFS z prohlížečů a mobilních aplikací bez odhalení API klíče.

Vývoj moderních webových aplikací často vyžaduje nahrávání souborů přímo z prohlížečů uživatelů do cloudového úložiště. Pokud jde však o bezpečnost nahrávání na IPFS, vývojáři čelí náročnému dilematu: jak umožnit nahrávání na straně klienta bez odhalení cenných API klíčů potenciálnímu zneužití?
Většina IPFS pinning služeb vás nutí do nepohodlné volby: buď zpracujete všechna nahrávání na straně serveru (vytváříte úzká hrdla a složitost), nebo vložíte API klíč do kódu klienta (bezpečnostní noční můra). IPFS.NINJA to řeší jedinečnou funkcí, kterou žádná jiná pinning služba nenabízí: podepsané tokeny pro nahrávání.

Problém s tradičními IPFS API klíči
Při vývoji aplikací na straně klienta, které potřebují nahrávat na IPFS, vývojáři obvykle narazí na několik bezpečnostních výzev:
Riziko odhalení API klíče
Vložení API klíčů přímo do JavaScriptu prohlížeče znamená, že kdokoli může zobrazit váš zdrojový kód a extrahovat vaše přihlašovací údaje. To by mohlo vést k:
- Neautorizovaným nahráváním spotřebovávajícím vaši kvótu úložiště
- Potenciálnímu zneužití vašeho účtu pinning služby
- Porušení bezpečnostních předpisů v podnikových prostředích
Úzká hrdla na straně serveru
Alternativa — směrování všech nahrávání přes váš backend — vytváří několik problémů:
- Zvýšené náklady na šířku pásma serveru
- Vyšší latence pro uživatele
- Složitější požadavky na infrastrukturu
- Potenciální jediné body selhání
Bezpečnost mobilních aplikací
Mobilní aplikace čelí podobným výzvám — API klíče uložené v balíčcích aplikací mohou být extrahovány reverzním inženýrstvím.
Představujeme tokeny pro nahrávání na IPFS
Podepsané tokeny pro nahrávání od IPFS.NINJA poskytují bezpečný kompromis. Takto fungují:
- Server generuje token: Váš backend vytvoří časově omezený, podepsaný token pomocí vašeho API klíče
- Klient obdrží token: Token je bezpečně přenesen do vaší frontendové aplikace
- Přímé nahrávání: Klienti nahrávají přímo do IPFS.NINJA pomocí podepsaného tokenu
- Automatické vypršení: Tokeny vyprší po stanovené době, čímž se omezí okno expozice
Tento přístup kombinuje bezpečnost autentizace na straně serveru s výkonnostními výhodami přímého nahrávání klientem.
Porozumění bezpečnosti tokenů pro nahrávání
Podepsané tokeny pro nahrávání používají kryptografické podpisy k zajištění autenticity bez odhalení vašeho hlavního API klíče. Každý token obsahuje:
- Časové razítko vypršení: Automatická invalidace po určené době
- Omezení použití: Volitelné limity počtu souborů nebo celkové velikosti
- Kryptografický podpis: Zabraňuje manipulaci nebo padělání
- Ověření vydavatele: Propojení s vaším autentizovaným účtem
Na rozdíl od API klíčů jsou tokeny pro nahrávání navrženy tak, aby mohly být bezpečně vloženy do kódu na straně klienta. I kdyby byly extrahovány, poskytují omezený přístup, který automaticky vyprší.
Implementace backendu: příklad Express.js
Pojďme vytvořit kompletní příklad ukazující, jak implementovat bezpečné nahrávání na IPFS na straně klienta. Nejprve backend Express.js, který generuje tokeny pro nahrávání:
// 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');
});Implementace frontendu: bezpečné nahrávání na straně klienta
Nyní kód frontendu, který bezpečně nahrává soubory pomocí podepsaného tokenu:
<!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>Pokročilé bezpečnostní aspekty
Při implementaci bezpečnosti nahrávání na IPFS s podepsanými tokeny zvažte tato další bezpečnostní opatření:
Omezení rozsahu tokenu
Konfigurujte tokeny s příslušnými omezeními:
// 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
})
});Validace obsahu
Vždy validujte nahraný obsah na vašem backendu:
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' });
}
});Omezení četnosti požadavků
Implementujte dodatečné omezení četnosti na vašem endpointu pro generování tokenů:
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);Výhody oproti tradičním přístupům
Podepsané tokeny pro nahrávání poskytují několik výhod oproti alternativním metodám zabezpečení nahrávání na IPFS:
Oproti proxy na straně serveru
- Výkon: Přímé nahrávání eliminuje spotřebu šířky pásma serveru
- Škálovatelnost: Žádná úzká hrdla serveru během období intenzivního nahrávání
- Náklady: Snížené náklady na šířku pásma a zpracování
- Uživatelský zážitek: Rychlejší nahrávání a lepší sledování průběhu
Oproti API klíčům na straně klienta
- Bezpečnost: Žádné riziko extrakce nebo zneužití API klíče
- Soulad s předpisy: Splňuje požadavky bezpečnostních auditů
- Řízení přístupu: Jemně nastavitelná oprávnění a automatické vypršení
- Monitoring: Lepší sledování zdrojů a vzorců nahrávání
Oproti jiným pinning službám
IPFS.NINJA je v současné době jedinou hlavní pinning službou nabízející podepsané tokeny pro nahrávání. Konkurenti jako Pinata vyžadují buď proxy na straně serveru, nebo odhalení API klíče na straně klienta, což z této funkce činí unikátní rozlišovací prvek.
Více informací o porovnání IPFS.NINJA s ostatními službami najdete v našem komplexním srovnávacím průvodci.
Tipy pro produkční nasazení
Při nasazování podepsaných tokenů pro nahrávání v produkci:
Konfigurace prostředí
Ukládejte citlivou konfiguraci bezpečně:
// 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']
};Monitoring a logování
Implementujte komplexní logování pro bezpečnostní 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
});Zpracování chyb
Implementujte robustní zpracování chyb, které neprozrazuje citlivé informace:
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.'
});
});Integrace s populárními frameworky
Podepsané tokeny pro nahrávání bezproblémově fungují s moderními webovými frameworky. Zde jsou rychlé příklady integrace:
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
};
}Závěr
Podepsané tokeny pro nahrávání řeší kritický bezpečnostní problém ve vývoji decentralizovaných aplikací. Poskytují bezpečný způsob, jak umožnit přímé nahrávání na straně klienta do IPFS bez odhalení API klíčů, čímž otevírají nové architektonické možnosti pro moderní webové aplikace.
Ať už vytváříte systém správy obsahu, NFT marketplace nebo jakoukoli aplikaci vyžadující bezpečné nahrávání souborů, tokeny pro nahrávání od IPFS.NINJA poskytují potřebnou bezpečnost a flexibilitu. Implementace je přímočará, bezpečnostní výhody jsou významné a výkonnostní zisky jsou podstatné.
Chcete-li se dozvědět více o základech IPFS, přečtěte si náš průvodce Co je IPFS pinning nebo prozkoumejte náš kompletní API tutoriál. Pro vývojáře hodnotící různé možnosti nabízí naše srovnání nejlepších IPFS pinning služeb komplexní přehled.
Jste připraveni implementovat bezpečné nahrávání na IPFS na straně klienta ve vaší aplikaci? Kombinace moderních bezpečnostních postupů a decentralizovaného úložiště činí tento přístup ideálním pro produkční aplikace, které potřebují škálovat při zachování bezpečnostních standardů.
Připraveni začít s pinningem? Vytvořte si bezplatný účet — 500 souborů, 1 GB úložiště, dedikovaná brána. Kreditní karta není vyžadována.
