· Nacho Coll · Guides · 10 min čitanja
IPFS Upload Tokeni: Sigurno Učitavanje s Klijentske Strane Bez Izlaganja API Ključeva
Naučite kako potpisani upload tokeni omogućuju sigurno učitavanje na IPFS iz preglednika i mobilnih aplikacija bez izlaganja vašeg API ključa.

Izgradnja modernih web aplikacija često zahtijeva učitavanje datoteka izravno iz preglednika korisnika u oblačno spremište. Međutim, kada je u pitanju sigurnost IPFS učitavanja, programeri se suočavaju s izazovnom dilemom: kako omogućiti učitavanje s klijentske strane bez izlaganja vaših vrijednih API ključeva potencijalnoj zlouporabi?
Većina IPFS pinning servisa prisiljava vas na neugodan izbor: obrađivati sva učitavanja na strani poslužitelja (stvarajući uska grla i složenost) ili ugraditi vaš API ključ u klijentski kod (sigurnosna noćna mora). IPFS.NINJA rješava to jedinstvenom značajkom koju nijedan drugi pinning servis ne nudi: potpisani upload tokeni.

Problem s Tradicionalnim IPFS API Ključevima
Prilikom izgradnje aplikacija na klijentskoj strani koje trebaju učitavati na IPFS, programeri se obično susreću s nekoliko sigurnosnih izazova:
Rizik Izlaganja API Ključa
Ugrađivanje API ključeva izravno u JavaScript preglednika znači da svatko može pregledati vaš izvorni kod i izvući vaše vjerodajnice. To može dovesti do:
- Neovlaštenih učitavanja koja troše vašu kvotu pohrane
- Potencijalne zlouporabe vašeg računa pinning servisa
- Kršenja sigurnosne usklađenosti u poslovnim okruženjima
Uska Grla na Strani Poslužitelja
Alternativa — usmjeravanje svih učitavanja kroz vaš backend — stvara nekoliko problema:
- Povećani troškovi propusnosti poslužitelja
- Veća latencija za korisnike
- Složeniji infrastrukturni zahtjevi
- Potencijalne jedinstvene točke kvara
Sigurnost Mobilnih Aplikacija
Mobilne aplikacije suočavaju se sa sličnim izazovima, gdje se API ključevi pohranjeni u paketima aplikacija mogu izvući obrnutim inženjeringom.
Predstavljamo IPFS Upload Tokene
Potpisani upload tokeni IPFS.NINJA pružaju siguran srednji put. Evo kako funkcioniraju:
- Poslužitelj generira token: Vaš backend stvara vremenski ograničen, potpisani token koristeći vaš API ključ
- Klijent prima token: Token se sigurno prenosi vašoj frontend aplikaciji
- Izravno učitavanje: Klijenti učitavaju izravno na IPFS.NINJA koristeći potpisani token
- Automatsko istjecanje: Tokeni istječu nakon zadanog trajanja, ograničavajući prozor izloženosti
Ovaj pristup kombinira sigurnost autentifikacije na strani poslužitelja s prednostima izvedbe izravnog klijentskog učitavanja.
Razumijevanje Sigurnosti Upload Tokena
Potpisani upload tokeni koriste kriptografske potpise za osiguravanje autentičnosti bez izlaganja vašeg glavnog API ključa. Svaki token sadrži:
- Vremensku oznaku istjecanja: Automatska nevažnost nakon navedenog trajanja
- Ograničenja korištenja: Opcionalna ograničenja broja datoteka ili ukupne veličine
- Kriptografski potpis: Sprječava neovlašteno mijenjanje ili krivotvorenje
- Provjera izdavatelja: Povezuje se s vašim autentificiranim računom
Za razliku od API ključeva, upload tokeni su dizajnirani za sigurno ugrađivanje u kod na klijentskoj strani. Čak i ako se izvuku, pružaju ograničen pristup koji automatski istječe.
Implementacija Backenda: Primjer s Express.js
Izgradimo potpuni primjer koji pokazuje kako implementirati sigurna učitavanja na IPFS s klijentske strane. Prvo, evo Express.js backenda koji generira upload tokene:
// 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');
});Implementacija Frontenda: Sigurno Klijentsko Učitavanje
Sada, evo frontend koda koji sigurno učitava datoteke koristeći potpisani 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 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>Napredna Sigurnosna Razmatranja
Prilikom implementacije sigurnosti IPFS učitavanja s potpisanim tokenima, razmotrite ove dodatne sigurnosne mjere:
Ograničenja Opsega Tokena
Konfigurirajte tokene s odgovarajućim ograničenjima:
// 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
})
});Validacija Sadržaja
Uvijek validirajte učitani sadržaj na vašem backendu:
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' });
}
});Ograničavanje Brzine
Implementirajte dodatno ograničavanje brzine na vašoj krajnjoj točki za generiranje tokena:
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);Prednosti u Odnosu na Tradicionalne Pristupe
Potpisani upload tokeni pružaju nekoliko prednosti u odnosu na alternativne metode sigurnosti IPFS učitavanja:
vs. Proxy na Strani Poslužitelja
- Izvedba: Izravna učitavanja eliminiraju korištenje propusnosti poslužitelja
- Skalabilnost: Nema uskih grla poslužitelja tijekom razdoblja visokog učitavanja
- Trošak: Smanjeni troškovi propusnosti i obrade
- Korisničko iskustvo: Bolje brzine učitavanja i praćenje napretka
vs. API Ključ na Strani Klijenta
- Sigurnost: Nema rizika od izvlačenja ili zlouporabe API ključa
- Usklađenost: Ispunjava zahtjeve sigurnosne revizije
- Kontrola pristupa: Detaljne dozvole i automatsko istjecanje
- Praćenje: Bolje praćenje izvora i obrazaca učitavanja
vs. Drugi Pinning Servisi
IPFS.NINJA je trenutno jedini veliki pinning servis koji nudi potpisane upload tokene. Konkurenti poput Pinate zahtijevaju ili proxy na strani poslužitelja ili izlaganje API ključa na strani klijenta, što ovo čini jedinstvenim razlikovnim faktorom.
Za više detalja o usporedbi IPFS.NINJA s drugim servisima, pogledajte naš sveobuhvatni vodič za usporedbu.
Savjeti za Produkcijsko Postavljanje
Prilikom postavljanja potpisanih upload tokena u produkciji:
Konfiguracija Okruženja
Pohranite osjetljivu konfiguraciju na siguran način:
// 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']
};Praćenje i Zapisivanje
Implementirajte sveobuhvatno zapisivanje za sigurnosno praćenje:
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
});Rukovanje Greškama
Implementirajte robusno rukovanje greškama koje ne otkriva osjetljive informacije:
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.'
});
});Integracija s Popularnim Okvirima
Potpisani upload tokeni besprijekorno rade s modernim web okvirima. Evo brzih primjera integracije:
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
};
}Zaključak
Potpisani upload tokeni rješavaju kritični sigurnosni izazov u razvoju decentraliziranih aplikacija. Pružajući siguran način omogućavanja izravnih učitavanja s klijentske strane na IPFS bez izlaganja API ključeva, otvaraju nove arhitekturne mogućnosti za moderne web aplikacije.
Bilo da gradite sustav za upravljanje sadržajem, NFT tržište ili bilo koju aplikaciju koja zahtijeva sigurna učitavanja datoteka, upload tokeni IPFS.NINJA pružaju sigurnost i fleksibilnost koja vam je potrebna. Implementacija je jednostavna, sigurnosne prednosti su značajne, a poboljšanja izvedbe su znatna.
Da biste saznali više o osnovama IPFS-a, pogledajte naš vodič o što je IPFS pinning ili istražite naš potpuni API vodič. Za programere koji procjenjuju različite opcije, naša usporedba najboljih IPFS pinning servisa pruža sveobuhvatne uvide.
Jeste li spremni implementirati sigurna učitavanja na IPFS s klijentske strane u svojoj aplikaciji? Kombinacija modernih sigurnosnih praksi i decentralizirane pohrane čini ovaj pristup idealnim za produkcijske aplikacije koje trebaju rasti uz održavanje sigurnosnih standarda.
Spremni za početak pinninga? Stvorite besplatan račun --- 500 datoteka, 1 GB pohrane, namjenski gateway. Kreditna kartica nije potrebna.
