· 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.

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.

IPFS Ninja

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:

  1. Servern genererar token: Din backend skapar ett tidsbegränsat, signerat token med din API-nyckel
  2. Klienten tar emot token: Tokenet överförs säkert till din frontend-applikation
  3. Direkt uppladdning: Klienter laddar upp direkt till IPFS.NINJA med det signerade tokenet
  4. 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.

Tillbaka till Bloggen

Relaterade artiklar

Visa alla artiklar »