· Nacho Coll · Guides · 9 min läsning
IPFS-uppladdningstoken: säkra klientsidiga uppladdningar utan att exponera API-nycklar
Lär dig hur signerade uppladdningstoken låter dig säkert ladda upp till IPFS från webbläsare och mobilappar utan att exponera din API-nyckel.

Att bygga moderna webbapplikationer kräver ofta att filer laddas upp direkt från användarnas webbläsare till molnlagring. Men när det gäller IPFS-uppladdningssäkerhet står utvecklare inför ett svårt dilemma: hur tillåter man klientsidiga uppladdningar utan att exponera värdefulla API-nycklar för potentiellt missbruk?
De flesta IPFS-pinning-tjänster tvingar dig till ett obekvämt val: antingen hanterar du alla uppladdningar på serversidan (skapar flaskhalsar och komplexitet) eller bäddar du in din API-nyckel i klientkod (en säkerhetsmardröm). IPFS.NINJA löser detta med en unik funktion som ingen annan pinning-tjänst erbjuder: signerade uppladdningstoken.

Problemet med traditionella IPFS API-nycklar
När du bygger klientsidiga applikationer som behöver ladda upp till IPFS stöter utvecklare vanligtvis på flera säkerhetsutmaningar:
Risk för exponering av API-nyckel
Att bädda in API-nycklar direkt i webbläsarens JavaScript innebär att vem som helst kan visa din källkod och extrahera dina uppgifter. Detta kan leda till:
- Obehöriga uppladdningar som förbrukar din lagringskvot
- Potentiellt missbruk av ditt pinning-tjänstkonto
- Överträdelser av säkerhetsefterlevnad i företagsmiljöer
Flaskhalsar på serversidan
Alternativet — att dirigera alla uppladdningar genom din backend — skapar flera problem:
- Ökade kostnader för serverbandbredd
- Högre latens för användare
- Mer komplexa infrastrukturkrav
- Potentiella enskilda felpunkter
Mobilappsäkerhet
Mobilapplikationer möter liknande utmaningar — API-nycklar lagrade i apppaket kan extraheras genom reverse engineering.
Introduktion av IPFS-uppladdningstoken
IPFS.NINJA:s signerade uppladdningstoken ger en säker mellanväg. Så här fungerar de:
- Servern genererar token: Din backend skapar ett tidsbegränsat, signerat token med din API-nyckel
- Klienten tar emot token: Tokenet överförs säkert till din frontend-applikation
- Direkt uppladdning: Klienter laddar upp direkt till IPFS.NINJA med det signerade tokenet
- Automatisk utgång: Token upphör efter en angiven varaktighet, vilket begränsar exponeringsperioden
Detta tillvägagångssätt kombinerar säkerheten hos serversidig autentisering med prestandafördelarna av direkta klientsidiga uppladdningar.
Förstå säkerheten för uppladdningstoken
Signerade uppladdningstoken använder kryptografiska signaturer för att säkerställa äkthet utan att exponera din huvudsakliga API-nyckel. Varje token innehåller:
- Utgångstidsstämpel: Automatisk ogiltigförklaring efter angiven varaktighet
- Användningsbegränsningar: Valfria gränser för antal filer eller total storlek
- Kryptografisk signatur: Förhindrar manipulation eller förfalskning
- Utfärdarverifiering: Koppling till ditt autentiserade konto
Till skillnad från API-nycklar är uppladdningstoken utformade för att säkert bäddas in i klientsidlig kod. Även om de extraheras ger de begränsad åtkomst som automatiskt upphör.
Backend-implementering: Express.js-exempel
Låt oss bygga ett komplett exempel som visar hur man implementerar säkra klientsidiga IPFS-uppladdningar. Först Express.js-backenden som genererar uppladdningstoken:
// 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-implementering: säker klientsidig uppladdning
Nu frontend-koden som säkert laddar upp filer med det signerade tokenet:
<!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 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 = ''; }
}
const uploader = new SecureIPFSUploader();
</script>
</body>
</html>Avancerade säkerhetsöverväganden
När du implementerar IPFS-uppladdningssäkerhet med signerade token, överväg dessa ytterligare säkerhetsåtgärder:
Begränsningar av tokenomfattning
Konfigurera token med lämpliga restriktioner:
// 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
})
});Innehållsvalidering
Validera alltid uppladdat innehåll på din backend:
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' });
}
});Frekvensbegränsning
Implementera ytterligare frekvensbegränsning på din tokengenererings-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);Fördelar jämfört med traditionella tillvägagångssätt
Signerade uppladdningstoken ger flera fördelar jämfört med alternativa IPFS-uppladdningssäkerhetsmetoder:
Jämfört med serversidig proxy
- Prestanda: Direkta uppladdningar eliminerar serverbandbreddanvändning
- Skalbarhet: Inga serverflaskhalsar under perioder med hög uppladdning
- Kostnad: Minskade bandbredds- och bearbetningskostnader
- Användarupplevelse: Bättre uppladdningshastigheter och framstegsuppföljning
Jämfört med klientsidiga API-nycklar
- Säkerhet: Ingen risk för extrahering eller missbruk av API-nyckel
- Efterlevnad: Uppfyller krav på säkerhetsgranskningar
- Åtkomstkontroll: Detaljerade behörigheter och automatisk utgång
- Övervakning: Bättre spårning av uppladdningskällor och mönster
Jämfört med andra pinning-tjänster
IPFS.NINJA är för närvarande den enda stora pinning-tjänsten som erbjuder signerade uppladdningstoken. Konkurrenter som Pinata kräver antingen serversidig proxy eller exponering av klientsidiga API-nycklar, vilket gör detta till en unik differentierare.
Mer information om hur IPFS.NINJA jämförs med andra tjänster finns i vår omfattande jämförelseguide.
Tips för produktionsdistribution
Vid distribution av signerade uppladdningstoken i produktion:
Miljökonfiguration
Lagra känslig konfiguration säkert:
// 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']
};Övervakning och loggning
Implementera omfattande loggning för säkerhetsövervakning:
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
});Felhantering
Implementera robust felhantering som inte läcker känslig information:
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.'
});
});Integration med populära ramverk
Signerade uppladdningstoken fungerar sömlöst med moderna webbramverk. Här är snabba integrationsexempel:
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
};
}Slutsats
Signerade uppladdningstoken löser en kritisk säkerhetsutmaning i decentraliserad applikationsutveckling. Genom att erbjuda ett säkert sätt att möjliggöra direkta klientsidiga uppladdningar till IPFS utan att exponera API-nycklar öppnar de nya arkitektoniska möjligheter för moderna webbapplikationer.
Oavsett om du bygger ett innehållshanteringssystem, NFT-marknadsplats eller någon applikation som kräver säkra filuppladdningar, ger IPFS.NINJA:s uppladdningstoken den säkerhet och flexibilitet du behöver. Implementeringen är enkel, säkerhetsfördelarna är betydande och prestandavinsterna är substantiella.
För att lära dig mer om IPFS-grunder, kolla in vår guide om vad IPFS pinning är eller utforska vår kompletta API-handledning. För utvecklare som utvärderar olika alternativ ger vår jämförelse av bästa IPFS-pinning-tjänster omfattande insikter.
Redo att implementera säkra klientsidiga IPFS-uppladdningar i din applikation? Kombinationen av moderna säkerhetsmetoder och decentraliserad lagring gör detta tillvägagångssätt idealiskt för produktionsapplikationer som behöver skala samtidigt som säkerhetsstandarder upprätthålls.
Redo att börja med pinning? Skapa ett gratis konto — 500 filer, 1 GB lagring, dedikerad gateway. Inget kreditkort krävs.
