· Nacho Coll · Guides  · 10 Min. Lesezeit

IPFS Upload-Tokens: Sichere Client-seitige Uploads ohne API-Schlüssel preiszugeben

Erfahren Sie, wie signierte Upload-Tokens es Ihnen ermöglichen, sicher aus Browsern und mobilen Apps auf IPFS hochzuladen, ohne Ihren API-Schlüssel preiszugeben.

Erfahren Sie, wie signierte Upload-Tokens es Ihnen ermöglichen, sicher aus Browsern und mobilen Apps auf IPFS hochzuladen, ohne Ihren API-Schlüssel preiszugeben.

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.

Zurück zum Blog

Verwandte Artikel

Alle Artikel anzeigen »