IPFS Upload-Tokens: Sichere Client-Uploads

Erfahre, wie signierte Upload-Tokens sichere direkte Uploads zu IPFS aus Browsern und mobilen Apps ermöglichen, ohne deinen API-Schlüssel preiszugeben.

Nacho Collvon Aktualisiert: 11 Min. Lesezeit
Erfahre, wie signierte Upload-Tokens sichere direkte Uploads zu IPFS aus Browsern und mobilen Apps ermöglichen, ohne deinen API-Schlüssel preiszugeben.
TL;DR
  • Erzeugen Sie signierte Upload-Tokens serverseitig, damit Browser und mobile Apps Ihren rohen API-Schlüssel nie zu sehen bekommen.
  • Tokens tragen einen Ablaufzeitstempel und Nutzungsbeschränkungen, was den Schaden begrenzt, falls eines je extrahiert wird.
  • Direkte Uploads vom Client zu IPFS per Token beseitigen Server-Bandbreiten-Engpässe vollständig.
  • Kryptografische Signaturen auf jedem Token verhindern Manipulation oder das Fälschen einer Upload-Berechtigung.

Moderne Webanwendungen erfordern häufig das Hochladen von Dateien direkt aus den Browsern der Benutzer in Cloud-Speicher. Wenn es jedoch um die Sicherheit von IPFS-Uploads geht, stehen Entwickler vor einem herausfordernden Dilemma: Wie ermöglicht man Client-seitige Uploads, ohne seine wertvollen API-Schlüssel einem möglichen Missbrauch auszusetzen?

Die meisten IPFS-Pinning-Dienste zwingen Sie zu einer unbequemen Wahl: Entweder alle Uploads Server-seitig verarbeiten (was Engpässe und Komplexität erzeugt) oder Ihren API-Schlüssel im Client-Code einbetten (ein Sicherheitsalptraum). IPFS.NINJA löst dieses Problem mit einem einzigartigen Feature, das kein anderer Pinning-Dienst bietet: signierte Upload-Tokens.

IPFS Ninja

Das Problem mit traditionellen IPFS-API-Schlüsseln#

Beim Erstellen von Client-seitigen Anwendungen, die auf IPFS hochladen müssen, stoßen Entwickler typischerweise auf mehrere Sicherheitsherausforderungen:

Risiko der API-Schlüssel-Offenlegung#

Das Einbetten von API-Schlüsseln direkt in Browser-JavaScript bedeutet, dass jeder Ihren Quellcode einsehen und Ihre Zugangsdaten extrahieren kann. Dies könnte führen zu:

  • Unautorisierte Uploads, die Ihr Speicherkontingent verbrauchen
  • Potenzieller Missbrauch Ihres Pinning-Dienst-Kontos
  • Verstöße gegen Sicherheits-Compliance in Unternehmensumgebungen

Server-seitige Engpässe#

Die Alternative --- alle Uploads über Ihr Backend zu leiten --- erzeugt mehrere Probleme:

  • Höhere Server-Bandbreitenkosten
  • Höhere Latenz für Benutzer
  • Komplexere Infrastrukturanforderungen
  • Mögliche Single Points of Failure

Sicherheit mobiler Apps#

Mobile Anwendungen stehen vor ähnlichen Herausforderungen, bei denen API-Schlüssel in App-Paketen durch Reverse Engineering extrahiert werden können.

Einführung der IPFS Upload-Tokens#

Die signierten Upload-Tokens von IPFS.NINJA bieten einen sicheren Mittelweg. So funktionieren sie:

  1. Server generiert den Token: Ihr Backend erstellt einen zeitlich begrenzten, signierten Token mit Ihrem API-Schlüssel
  2. Client empfängt den Token: Der Token wird sicher an Ihre Frontend-Anwendung übertragen
  3. Direkter Upload: Clients laden direkt zu IPFS.NINJA hoch, wobei sie den signierten Token verwenden
  4. Automatischer Ablauf: Tokens laufen nach einer festgelegten Dauer ab und begrenzen das Expositionsfenster

Dieser Ansatz kombiniert die Sicherheit der Server-seitigen Authentifizierung mit den Leistungsvorteilen direkter Client-Uploads.

Upload-Token-Sicherheit verstehen#

Signierte Upload-Tokens verwenden kryptographische Signaturen, um die Authentizität zu gewährleisten, ohne Ihren Haupt-API-Schlüssel preiszugeben. Jeder Token enthält:

  • Ablauf-Zeitstempel: Automatische Ungültigmachung nach der angegebenen Dauer
  • Nutzungsbeschränkungen: Optionale Limits für Dateianzahl oder Gesamtgröße
  • Kryptographische Signatur: Verhindert Manipulation oder Fälschung
  • Aussteller-Verifizierung: Verknüpfung mit Ihrem authentifizierten Konto

Im Gegensatz zu API-Schlüsseln sind Upload-Tokens so konzipiert, dass sie sicher in Client-seitigem Code eingebettet werden können. Selbst wenn sie extrahiert werden, bieten sie eingeschränkten Zugriff, der automatisch abläuft.

Backend-Implementierung: Express.js-Beispiel#

Lassen Sie uns ein vollständiges Beispiel erstellen, das zeigt, wie sichere Client-seitige IPFS-Uploads implementiert werden. Zunächst das Express.js-Backend, das Upload-Tokens generiert:

// 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-Implementierung: Sicherer Client-Upload#

Hier ist nun der Frontend-Code, der Dateien sicher mit dem signierten Token hochlädt:

<!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>

Erweiterte Sicherheitsüberlegungen#

Bei der Implementierung von IPFS-Upload-Sicherheit mit signierten Tokens sollten Sie diese zusätzlichen Sicherheitsmaßnahmen berücksichtigen:

Token-Bereichsbeschränkungen#

Konfigurieren Sie Tokens mit angemessenen Einschränkungen:

// 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
  })
});

Inhaltsvalidierung#

Validieren Sie hochgeladene Inhalte immer auf Ihrem 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' });
  }
});

Ratenbegrenzung#

Implementieren Sie eine zusätzliche Ratenbegrenzung auf Ihrem Token-Generierungs-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);

Vorteile gegenüber traditionellen Ansätzen#

Signierte Upload-Tokens bieten mehrere Vorteile gegenüber alternativen IPFS-Upload-Sicherheitsmethoden:

vs. Server-seitiges Proxying#

  • Leistung: Direkte Uploads eliminieren die Server-Bandbreitennutzung
  • Skalierbarkeit: Keine Server-Engpässe bei hohem Upload-Aufkommen
  • Kosten: Reduzierte Bandbreiten- und Verarbeitungskosten
  • Benutzererfahrung: Bessere Upload-Geschwindigkeiten und Fortschrittsverfolgung

vs. Client-seitige API-Schlüssel#

  • Sicherheit: Kein Risiko der API-Schlüssel-Extraktion oder des Missbrauchs
  • Compliance: Erfüllt Sicherheitsaudit-Anforderungen
  • Zugriffskontrolle: Feingranulare Berechtigungen und automatischer Ablauf
  • Überwachung: Bessere Verfolgung von Upload-Quellen und -Mustern

vs. Andere Pinning-Dienste#

IPFS.NINJA ist derzeit der einzige große Pinning-Dienst, der signierte Upload-Tokens anbietet. Wettbewerber wie Pinata erfordern entweder Server-seitiges Proxying oder Client-seitige API-Schlüssel-Offenlegung, was dies zu einem einzigartigen Differenzierungsmerkmal macht.

Weitere Details zum Vergleich von IPFS.NINJA mit anderen Diensten finden Sie in unserem umfassenden Vergleichsleitfaden.

Tipps für den Produktionseinsatz#

Beim Einsatz signierter Upload-Tokens in der Produktion:

Umgebungskonfiguration#

Speichern Sie sensible Konfigurationen sicher:

// 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']
};

Überwachung und Protokollierung#

Implementieren Sie umfassende Protokollierung für die Sicherheitsüberwachung:

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
});

Fehlerbehandlung#

Implementieren Sie eine robuste Fehlerbehandlung, die keine sensiblen Informationen preisgibt:

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 mit populären Frameworks#

Signierte Upload-Tokens funktionieren nahtlos mit modernen Web-Frameworks. Hier sind schnelle Integrationsbeispiele:

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
  };
}

Fazit#

Signierte Upload-Tokens lösen eine kritische Sicherheitsherausforderung in der Entwicklung dezentraler Anwendungen. Indem sie einen sicheren Weg bieten, direkte Client-seitige Uploads zu IPFS zu ermöglichen, ohne API-Schlüssel preiszugeben, eröffnen sie neue architektonische Möglichkeiten für moderne Webanwendungen.

Ob Sie ein Content-Management-System, einen NFT-Marktplatz oder eine andere Anwendung bauen, die sichere Datei-Uploads erfordert --- die Upload-Tokens von IPFS.NINJA bieten die Sicherheit und Flexibilität, die Sie benötigen. Die Implementierung ist unkompliziert, die Sicherheitsvorteile sind erheblich und die Leistungsgewinne sind substanziell.

Um mehr über die Grundlagen von IPFS zu erfahren, lesen Sie unseren Leitfaden zu Was ist IPFS-Pinning oder erkunden Sie unser vollständiges API-Tutorial. Für Entwickler, die verschiedene Optionen evaluieren, bietet unser Vergleich der besten IPFS-Pinning-Dienste umfassende Einblicke.

Bereit, sichere Client-seitige IPFS-Uploads in Ihrer Anwendung zu implementieren? Die Kombination aus modernen Sicherheitspraktiken und dezentralem Speicher macht diesen Ansatz ideal für Produktionsanwendungen, die skalieren müssen und gleichzeitig Sicherheitsstandards einhalten.

Bereit mit dem Pinning zu starten? Erstellen Sie ein kostenloses Konto --- 500 Dateien, 1 GB Speicher, dediziertes Gateway. Keine Kreditkarte erforderlich.

Nacho Coll

Über den Autor

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.

Zurück zum Blog

Verwandte Artikel