IPFS Upload Tokens: Veilige Client-side Uploads
Ontdek hoe gesigneerde upload-tokens veilige directe uploads naar IPFS mogelijk maken vanuit browsers en mobiele apps, zonder je API-sleutel bloot te stellen.
Nacho founded IPFS.NINJA to make content-addressed storage feel as simple as an S3 PUT — a single API call, a permanent CID, no wallets or peer discovery to reason about.

- Genereer ondertekende upload tokens server-side zodat browsers en mobiele apps nooit je ruwe API-sleutel te zien krijgen.
- Tokens hebben een vervaltijdstempel en gebruiksbeperkingen, wat de schade beperkt als er ooit een wordt onderschept.
- Directe client-naar-IPFS uploads via tokens elimineren serverbandbreedte-knelpunten volledig.
- Cryptografische handtekeningen op elk token voorkomen knoeien met of vervalsen van een nep-uploadcredential.
Het bouwen van moderne webapplicaties vereist vaak het uploaden van bestanden rechtstreeks vanuit de browsers van gebruikers naar cloud-opslag. Echter, als het gaat om de beveiliging van IPFS-uploads, staan ontwikkelaars voor een uitdagend dilemma: hoe sta je client-side uploads toe zonder je waardevolle API-sleutels bloot te stellen aan mogelijk misbruik?
De meeste IPFS-pinning-diensten dwingen je tot een ongemakkelijke keuze: alle uploads server-side afhandelen (wat bottlenecks en complexiteit creëert) of je API-sleutel inbedden in client-code (een beveiligingsnachtmerrie). IPFS.NINJA lost dit op met een unieke functie die geen enkele andere pinning-dienst biedt: ondertekende upload tokens.

Hoe IPFS Upload Tokens Veilige Client-side Toegang Mogelijk Maken#
Bij het bouwen van client-side applicaties die naar IPFS moeten uploaden, stuiten ontwikkelaars doorgaans op verschillende beveiligingsuitdagingen:
Risico van API-sleutel Blootstelling#
Het direct inbedden van API-sleutels in browser-JavaScript betekent dat iedereen je broncode kan bekijken en je referenties kan extraheren. Dit kan leiden tot:
- Ongeautoriseerde uploads die je opslagquotum verbruiken
- Potentieel misbruik van je pinning-dienst account
- Schendingen van beveiligingsconformiteit in bedrijfsomgevingen
Server-side Bottlenecks#
Het alternatief --- alle uploads via je backend routeren --- creëert verschillende problemen:
- Hogere bandbreedtekosten voor de server
- Hogere latentie voor gebruikers
- Complexere infrastructuurvereisten
- Mogelijke single points of failure
Beveiliging van Mobiele Apps#
Mobiele applicaties staan voor vergelijkbare uitdagingen, waarbij API-sleutels opgeslagen in app-pakketten kunnen worden geëxtraheerd door reverse engineering.
Introductie van IPFS Upload Tokens#
De ondertekende upload tokens van IPFS.NINJA bieden een veilige middenweg. Zo werken ze:
- Server genereert token: Je backend maakt een tijdgebonden, ondertekend token met je API-sleutel
- Client ontvangt token: Het token wordt veilig naar je frontend-applicatie verzonden
- Directe upload: Clients uploaden direct naar IPFS.NINJA met het ondertekende token
- Automatisch verlopen: Tokens verlopen na een ingestelde duur, waardoor het blootstellingsvenster beperkt wordt
Deze aanpak combineert de beveiliging van server-side authenticatie met de prestatievoordelen van directe client-uploads.
Upload Token Beveiliging Begrijpen#
Ondertekende upload tokens gebruiken cryptografische handtekeningen om authenticiteit te garanderen zonder je hoofd-API-sleutel bloot te stellen. Elk token bevat:
- Verlooptijdstempel: Automatische ongeldigverklaring na de opgegeven duur
- Gebruiksbeperkingen: Optionele limieten op het aantal bestanden of totale grootte
- Cryptografische handtekening: Voorkomt manipulatie of vervalsing
- Uitgever-verificatie: Koppelt terug naar je geauthenticeerde account
In tegenstelling tot API-sleutels zijn upload tokens ontworpen om veilig ingebed te worden in client-side code. Zelfs als ze worden geëxtraheerd, bieden ze beperkte toegang die automatisch verloopt.
Backend Implementatie: Express.js Voorbeeld#
Laten we een volledig voorbeeld bouwen dat laat zien hoe je veilige client-side IPFS-uploads implementeert. Eerst het Express.js-backend dat upload tokens genereert:
// 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 {
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 Implementatie: Veilige Client Upload#
Hier is de frontend-code die bestanden veilig uploadt met het ondertekende 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');
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)); });
fileInput.addEventListener('change', (e) => { this.handleFiles(Array.from(e.target.files)); });
}
async getUploadToken() {
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();
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 {
const results = await Promise.all(files.map(file => this.uploadFile(file)));
this.displayResults(results);
const successful = results.filter(r => r.success).length;
if (successful === results.length) this.showStatus(`✅ Successfully uploaded ${successful} file(s) to IPFS!`, 'success');
else this.showStatus(`⚠️ Uploaded ${successful}/${results.length} 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 = () => resolve(reader.result.split(',')[1]); reader.onerror = error => reject(error); }); }
showStatus(message, type) { const s = document.getElementById('status'); s.className = `status ${type}`; s.textContent = message; }
displayResults(results) {
document.getElementById('results').innerHTML = '<h3>Upload Results:</h3>' + results.map(r => `<div class="status ${r.success ? 'success' : 'error'}" style="margin: 10px 0;"><strong>${r.filename}</strong><br>${r.success ? `✅ CID: ${r.cid}<br>📊 Size: ${r.size} MB<br>🔗 URL: <a href="${r.httpUrl}" target="_blank">${r.httpUrl}</a>` : `❌ Error: ${r.error}`}</div>`).join('');
}
clearResults() { document.getElementById('results').innerHTML = ''; }
}
const uploader = new SecureIPFSUploader();
</script>
</body>
</html>Geavanceerde Beveiligingsoverwegingen#
Token Scope Beperkingen#
const restrictedToken = await fetch('https://api.ipfs.ninja/upload-tokens', {
method: 'POST',
headers: { 'X-Api-Key': IPFS_API_KEY },
body: JSON.stringify({
expiresIn: '30m', maxUploads: 5, maxSizeMB: 10,
allowedMimeTypes: ['image/jpeg', 'image/png'],
ipWhitelist: ['192.168.1.0/24']
})
});Inhoudsvalidatie#
app.post('/api/validate-upload', async (req, res) => {
const { cid } = req.body;
try {
const response = await fetch(`https://ipfs.ninja/ipfs/${cid}`);
const contentType = response.headers.get('content-type');
if (!isValidContentType(contentType)) {
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#
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);Voordelen ten Opzichte van Traditionele Aanpakken#
vs. Server-side Proxying#
- Prestaties: Directe uploads elimineren server-bandbreedtegebruik
- Schaalbaarheid: Geen server-bottlenecks tijdens piekperioden
- Kosten: Lagere bandbreedte- en verwerkingskosten
- Gebruikerservaring: Betere uploadsnelheden en voortgangsregistratie
vs. Client-side API-sleutels#
- Beveiliging: Geen risico op API-sleutel extractie of misbruik
- Conformiteit: Voldoet aan beveiligingsaudit-eisen
- Toegangscontrole: Gedetailleerde machtigingen en automatisch verlopen
- Monitoring: Beter inzicht in uploadbronnen en -patronen
vs. Andere Pinning-diensten#
IPFS.NINJA is momenteel de enige grote pinning-dienst die ondertekende upload tokens aanbiedt. Concurrenten zoals Pinata vereisen server-side proxying of client-side API-sleutel blootstelling.
Bekijk onze uitgebreide vergelijkingsgids voor meer details.
Tips voor Productie-implementatie#
Omgevingsconfiguratie#
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 en Logging#
const winston = require('winston');
const logger = winston.createLogger({
level: 'info', format: winston.format.json(),
transports: [new winston.transports.File({ filename: 'upload-security.log' })]
});
logger.info('Upload token generated', {
userId: req.user.id, clientIP: req.ip, userAgent: req.get('User-Agent'), expiresAt: tokenData.expiresAt
});Foutafhandeling#
app.use((error, req, res, next) => {
logger.error('Upload token error', { error: error.message, stack: error.stack, userId: req.user?.id, endpoint: req.path });
res.status(500).json({ success: false, error: 'An internal error occurred. Please try again.' });
});Integratie met Populaire Frameworks#
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 };
}Conclusie#
Ondertekende upload tokens lossen een kritieke beveiligingsuitdaging op in de ontwikkeling van gedecentraliseerde applicaties. Door een veilige manier te bieden om directe client-side uploads naar IPFS mogelijk te maken zonder API-sleutels bloot te stellen, openen ze nieuwe architecturale mogelijkheden voor moderne webapplicaties.
Of je nu een content management systeem, een NFT-marktplaats of een andere applicatie bouwt die veilige bestandsuploads vereist --- de upload tokens van IPFS.NINJA bieden de beveiliging en flexibiliteit die je nodig hebt.
Lees meer over de basisprincipes van IPFS in onze gids over wat is IPFS-pinning of verken onze volledige API-tutorial. Voor de beste IPFS-pinning-diensten vergelijking bieden we uitgebreide inzichten.
Klaar om te beginnen met pinning? Maak een gratis account aan --- 500 bestanden, 1 GB opslag, dedicated gateway. Geen creditcard vereist.
| Aanpak | API-sleutel in client? | Verloopt automatisch? | Gedetailleerde limieten? |
|---|---|---|---|
| Volledige API-sleutel insluiten | Ja (onveilig) | Nee | Nee |
| Serverside-proxy | Nee | N.v.t. | N.v.t. |
| IPFS.NINJA upload-token | Nee | Ja | Ja |
Veelgestelde vragen#
Wat zijn IPFS upload-tokens en hoe verschillen ze van API-sleutels? IPFS upload-tokens zijn kortlevende, cryptografisch ondertekende credentials die door uw server worden gegenereerd met behulp van uw API-sleutel. In tegenstelling tot API-sleutels, die volledige toegang tot het account verlenen, hebben upload-tokens een ingebouwde vervaldatum, optionele limieten voor bestandsaantal en -grootte, en zijn ze ontworpen om veilig te worden gebruikt in client-side code.
Kan ik beperken wat een upload-token kan doen? Ja. Bij het genereren van een token kunt u expiresIn (bijv. ‘1h’), maxUploads (bijv. 10 bestanden), maxSizeMB (bijv. 50 MB), allowedMimeTypes (bijv. [‘image/jpeg’]) en ipWhitelist (bijv. CIDR-bereiken) instellen. Zelfs als het token wordt geëxtraheerd uit client-side code, kan een aanvaller alleen uploaden binnen die beperkingen voordat het token verloopt.
Hoe genereer ik een upload-token in Node.js / Express? POST naar https://api.ipfs.ninja/upload-tokens met uw API-sleutel in de X-Api-Key-header en een JSON-body met expiresIn, maxUploads en maxSizeMB. Het antwoord bevat een ondertekende token-string en een expiresAt-tijdstempel. Geef dat token door aan uw frontend; de client stuurt het vervolgens als Authorization: Signed <token> bij het aanroepen van het upload-eindpunt.
Is IPFS.NINJA de enige pinning-service die ondertekende upload-tokens ondersteunt? Ja, per 2026 is IPFS.NINJA de enige grote IPFS-pinning-service die ondertekende, scoped upload-tokens aanbiedt. Andere diensten vereisen dat alle uploads via uw backend worden gerouteerd, of dat een volledige API-sleutel in client-code wordt ingebed.
Wat gebeurt er als een upload-token wordt gestolen of gelekt? De blootstelling wordt beperkt door de ingebouwde beperkingen van het token: het verloopt automatisch na de geconfigureerde duur, kan alleen worden gebruikt voor een beperkt aantal uploads, kan de geconfigureerde groottelimiet niet overschrijden, en kan niet worden gebruikt om uw gepinde bestanden te vermelden, verwijderen of beheren — die bewerkingen vereisen nog steeds uw hoofd-API-sleutel.
Over dit artikel
Dit artikel is met AI-ondersteuning geschreven, door mensen gecontroleerd en geverifieerd op het live IPFS.NINJA-platform vóór publicatie. Lees hoe we AI gebruiken in onze content .

Over de auteur
Nacho Coll
Founder & Engineer at IPFS.NINJA
Nacho founded IPFS.NINJA to make content-addressed storage feel as simple as an S3 PUT — a single API call, a permanent CID, no wallets or peer discovery to reason about. Writes about IPFS internals, decentralized storage patterns, and the pinning-service landscape from the operator side of the wire.
