· Nacho Coll · Guides · 10 min czytania
Tokeny przesyłania IPFS: bezpieczne przesyłanie po stronie klienta bez ujawniania kluczy API
Dowiedz się, jak podpisane tokeny przesyłania pozwalają bezpiecznie przesyłać pliki do IPFS z przeglądarek i aplikacji mobilnych bez ujawniania klucza API.

Tworzenie nowoczesnych aplikacji internetowych często wymaga przesyłania plików bezpośrednio z przeglądarek użytkowników do chmury. Jednak jeśli chodzi o bezpieczeństwo przesyłania IPFS, programiści stają przed trudnym dylematem: jak umożliwić przesyłanie po stronie klienta bez ujawniania cennych kluczy API potencjalnym nadużyciom?
Większość usług pinningu IPFS zmusza do niewygodnego wyboru: albo obsługujesz wszystkie przesyłania po stronie serwera (tworząc wąskie gardła i komplikacje), albo osadzasz klucz API w kodzie klienta (koszmar bezpieczeństwa). IPFS.NINJA rozwiązuje ten problem unikalną funkcją, której nie oferuje żadna inna usługa pinningu: podpisane tokeny przesyłania.

Problem z tradycyjnymi kluczami API IPFS
Podczas tworzenia aplikacji po stronie klienta, które muszą przesyłać dane do IPFS, programiści napotykają kilka wyzwań związanych z bezpieczeństwem:
Ryzyko ujawnienia klucza API
Osadzanie kluczy API bezpośrednio w JavaScript przeglądarki oznacza, że każdy może przeglądać kod źródłowy i wyodrębnić dane uwierzytelniające. Może to prowadzić do:
- Nieautoryzowanych przesyłań zużywających limit przestrzeni dyskowej
- Potencjalnego nadużycia konta usługi pinningu
- Naruszeń zgodności z wymogami bezpieczeństwa w środowiskach korporacyjnych
Wąskie gardła po stronie serwera
Alternatywa — kierowanie wszystkich przesyłań przez backend — tworzy kilka problemów:
- Zwiększone koszty przepustowości serwera
- Większe opóźnienia dla użytkowników
- Bardziej złożone wymagania infrastrukturalne
- Potencjalne pojedyncze punkty awarii
Bezpieczeństwo aplikacji mobilnych
Aplikacje mobilne napotykają podobne wyzwania — klucze API przechowywane w pakietach aplikacji mogą zostać wyodrębnione przez inżynierię wsteczną.
Przedstawiamy tokeny przesyłania IPFS
Podpisane tokeny przesyłania IPFS.NINJA zapewniają bezpieczny kompromis. Oto jak działają:
- Serwer generuje token: Twój backend tworzy ograniczony czasowo, podpisany token przy użyciu klucza API
- Klient otrzymuje token: Token jest bezpiecznie przekazywany do Twojej aplikacji frontendowej
- Bezpośrednie przesyłanie: Klienci przesyłają bezpośrednio do IPFS.NINJA za pomocą podpisanego tokenu
- Automatyczne wygaśnięcie: Tokeny wygasają po określonym czasie, ograniczając okno ekspozycji
To podejście łączy bezpieczeństwo uwierzytelniania po stronie serwera z zaletami wydajności bezpośredniego przesyłania po stronie klienta.
Zrozumienie bezpieczeństwa tokenów przesyłania
Podpisane tokeny przesyłania używają podpisów kryptograficznych, aby zapewnić autentyczność bez ujawniania głównego klucza API. Każdy token zawiera:
- Znacznik czasu wygaśnięcia: Automatyczne unieważnienie po określonym czasie
- Ograniczenia użycia: Opcjonalne limity liczby plików lub całkowitego rozmiaru
- Podpis kryptograficzny: Zapobiega manipulacji lub fałszerstwu
- Weryfikacja wydawcy: Powiązanie z Twoim uwierzytelnionym kontem
W przeciwieństwie do kluczy API, tokeny przesyłania są zaprojektowane tak, aby można je było bezpiecznie osadzać w kodzie po stronie klienta. Nawet jeśli zostaną wyodrębnione, zapewniają ograniczony dostęp, który automatycznie wygasa.
Implementacja backendu: przykład Express.js
Zbudujmy kompletny przykład pokazujący, jak zaimplementować bezpieczne przesyłanie IPFS po stronie klienta. Najpierw backend Express.js, który generuje tokeny przesyłania:
// 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');
});Implementacja frontendu: bezpieczne przesyłanie po stronie klienta
Teraz kod frontendowy, który bezpiecznie przesyła pliki za pomocą podpisanego 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>Zaawansowane zagadnienia bezpieczeństwa
Przy wdrażaniu bezpieczeństwa przesyłania IPFS z podpisanymi tokenami warto rozważyć następujące dodatkowe środki bezpieczeństwa:
Ograniczenia zakresu tokenu
Konfiguruj tokeny z odpowiednimi restrykcjami:
// 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
})
});Walidacja zawartości
Zawsze waliduj przesłaną zawartość na swoim backendzie:
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' });
}
});Ograniczanie częstotliwości zapytań
Zaimplementuj dodatkowe ograniczanie częstotliwości zapytań na endpoincie generowania tokenów:
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);Zalety w porównaniu z tradycyjnymi podejściami
Podpisane tokeny przesyłania oferują kilka zalet w porównaniu z alternatywnymi metodami bezpieczeństwa przesyłania IPFS:
W porównaniu z proxy po stronie serwera
- Wydajność: Bezpośrednie przesyłanie eliminuje zużycie przepustowości serwera
- Skalowalność: Brak wąskich gardeł serwera w okresach intensywnego przesyłania
- Koszt: Zmniejszone koszty przepustowości i przetwarzania
- Doświadczenie użytkownika: Lepsza prędkość przesyłania i śledzenie postępu
W porównaniu z kluczami API po stronie klienta
- Bezpieczeństwo: Brak ryzyka wyodrębnienia lub nadużycia klucza API
- Zgodność: Spełnia wymagania audytów bezpieczeństwa
- Kontrola dostępu: Szczegółowe uprawnienia i automatyczne wygaśnięcie
- Monitorowanie: Lepsze śledzenie źródeł i wzorców przesyłania
W porównaniu z innymi usługami pinningu
IPFS.NINJA jest obecnie jedyną główną usługą pinningu oferującą podpisane tokeny przesyłania. Konkurenci tacy jak Pinata wymagają proxy po stronie serwera lub ujawnienia klucza API po stronie klienta, co czyni tę funkcję unikalnym wyróżnikiem.
Więcej szczegółów na temat porównania IPFS.NINJA z innymi usługami znajdziesz w naszym kompleksowym przewodniku porównawczym.
Wskazówki dotyczące wdrożenia produkcyjnego
Podczas wdrażania podpisanych tokenów przesyłania w produkcji:
Konfiguracja środowiska
Przechowuj poufną konfigurację w bezpieczny sposób:
// 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']
};Monitorowanie i logowanie
Zaimplementuj kompleksowe logowanie do monitorowania bezpieczeństwa:
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
});Obsługa błędów
Zaimplementuj solidną obsługę błędów, która nie ujawnia poufnych informacji:
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.'
});
});Integracja z popularnymi frameworkami
Podpisane tokeny przesyłania działają bezproblemowo z nowoczesnymi frameworkami webowymi. Oto szybkie przykłady integracji:
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
};
}Podsumowanie
Podpisane tokeny przesyłania rozwiązują krytyczny problem bezpieczeństwa w tworzeniu zdecentralizowanych aplikacji. Zapewniając bezpieczny sposób umożliwienia bezpośredniego przesyłania po stronie klienta do IPFS bez ujawniania kluczy API, otwierają nowe możliwości architektoniczne dla nowoczesnych aplikacji internetowych.
Niezależnie od tego, czy budujesz system zarządzania treścią, marketplace NFT, czy jakąkolwiek aplikację wymagającą bezpiecznego przesyłania plików, tokeny przesyłania IPFS.NINJA zapewniają potrzebne bezpieczeństwo i elastyczność. Implementacja jest prosta, korzyści bezpieczeństwa znaczące, a zyski wydajności istotne.
Aby dowiedzieć się więcej o podstawach IPFS, zapoznaj się z naszym przewodnikiem Czym jest pinning IPFS lub sprawdź nasz kompletny samouczek API. Dla programistów oceniających różne opcje, nasze porównanie najlepszych usług pinningu IPFS dostarcza kompleksowych informacji.
Gotowy do wdrożenia bezpiecznego przesyłania IPFS po stronie klienta w swojej aplikacji? Połączenie nowoczesnych praktyk bezpieczeństwa i zdecentralizowanego przechowywania danych czyni to podejście idealnym dla produkcyjnych aplikacji, które muszą się skalować przy zachowaniu standardów bezpieczeństwa.
Gotowy, aby zacząć pinning? Utwórz darmowe konto — 500 plików, 1 GB przestrzeni dyskowej, dedykowana brama. Karta kredytowa nie jest wymagana.
