· Nacho Coll · Guides  · 11 min de lectura

Tokens de Pujada IPFS: Pujades del Costat del Client Segures Sense Exposar les Claus API

Apreneu com els tokens de pujada signats us permeten pujar fitxers a IPFS de forma segura des de navegadors i aplicacions mòbils sense exposar la vostra clau API.

Apreneu com els tokens de pujada signats us permeten pujar fitxers a IPFS de forma segura des de navegadors i aplicacions mòbils sense exposar la vostra clau API.

Construir aplicacions web modernes sovint requereix pujar fitxers directament des dels navegadors dels usuaris a l’emmagatzematge al núvol. No obstant això, quan es tracta de la seguretat de pujada IPFS, els desenvolupadors s’enfronten a un dilema desafiant: com permetre pujades del costat del client sense exposar les vostres valuoses claus API a un possible ús indegut?

La majoria de serveis de pinning IPFS us obliguen a fer una elecció incòmoda: gestionar totes les pujades al costat del servidor (creant colls d’ampolla i complexitat) o incrustar la vostra clau API al codi del client (un malson de seguretat). IPFS.NINJA resol això amb una característica única que cap altre servei de pinning ofereix: tokens de pujada signats.

IPFS Ninja

El Problema amb les Claus API IPFS Tradicionals

Quan es construeixen aplicacions del costat del client que necessiten pujar a IPFS, els desenvolupadors típicament troben diversos reptes de seguretat:

Risc d’Exposició de la Clau API

Incrustar claus API directament al JavaScript del navegador significa que qualsevol pot veure el vostre codi font i extreure les vostres credencials. Això podria provocar:

  • Pujades no autoritzades que consumeixen la vostra quota d’emmagatzematge
  • Possible abús del vostre compte de servei de pinning
  • Violacions de compliment de seguretat en entorns empresarials

Colls d’Ampolla del Costat del Servidor

L’alternativa---encaminar totes les pujades a través del vostre backend---crea diversos problemes:

  • Augment dels costos d’ample de banda del servidor
  • Major latència per als usuaris
  • Requisits d’infraestructura més complexos
  • Possibles punts únics de fallada

Seguretat d’Aplicacions Mòbils

Les aplicacions mòbils s’enfronten a reptes similars, on les claus API emmagatzemades als paquets d’aplicacions es poden extreure mitjançant enginyeria inversa.

Presentant els Tokens de Pujada IPFS

Els tokens de pujada signats d’IPFS.NINJA proporcionen un punt mig segur. Així és com funcionen:

  1. El servidor genera el token: El vostre backend crea un token signat amb límit de temps utilitzant la vostra clau API
  2. El client rep el token: El token es transmet de forma segura a la vostra aplicació frontend
  3. Pujada directa: Els clients pugen directament a IPFS.NINJA utilitzant el token signat
  4. Caducitat automàtica: Els tokens caduquen després d’una durada establerta, limitant la finestra d’exposició

Aquesta aproximació combina la seguretat de l’autenticació del costat del servidor amb els beneficis de rendiment de les pujades directes del client.

Entenent la Seguretat dels Upload Tokens

Els tokens de pujada signats utilitzen signatures criptogràfiques per assegurar l’autenticitat sense exposar la vostra clau API principal. Cada token conté:

  • Marca de temps de caducitat: Invalidació automàtica després de la durada especificada
  • Restriccions d’ús: Límits opcionals en el nombre de fitxers o la mida total
  • Signatura criptogràfica: Impedeix la manipulació o la falsificació
  • Verificació de l’emissor: Enllaça de tornada al vostre compte autenticat

A diferència de les claus API, els tokens de pujada estan dissenyats per ser incrustats de forma segura al codi del costat del client. Fins i tot si s’extreuen, proporcionen un accés limitat que caduca automàticament.

Implementació del Backend: Exemple amb Express.js

Construïm un exemple complet que mostra com implementar pujades IPFS segures del costat del client. Primer, aquí teniu el backend Express.js que genera tokens de pujada:

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

Implementació del Frontend: Pujada Segura del Client

Ara, aquí teniu el codi del frontend que puja fitxers de forma segura utilitzant el token signat:

<!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 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 = () => { 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>

Consideracions de Seguretat Avançades

Quan implementeu seguretat de pujada IPFS amb tokens signats, considereu aquestes mesures de seguretat addicionals:

Limitacions d’Abast del Token

Configureu els tokens amb restriccions adequades:

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

Validació de Contingut

Valideu sempre el contingut pujat al vostre 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' });
  }
});

Limitació de Velocitat

Implementeu limitació de velocitat addicional al vostre endpoint de generació de tokens:

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

Avantatges Respecte als Enfocaments Tradicionals

Els tokens de pujada signats proporcionen diversos avantatges respecte als mètodes alternatius de seguretat de pujada IPFS:

vs. Proxy del Costat del Servidor

  • Rendiment: Les pujades directes eliminen l’ús d’ample de banda del servidor
  • Escalabilitat: Sense colls d’ampolla del servidor durant períodes d’alta pujada
  • Cost: Reducció de costos d’ample de banda i processament
  • Experiència d’Usuari: Millor velocitat de pujada i seguiment del progrés

vs. Clau API del Costat del Client

  • Seguretat: Sense risc d’extracció o ús indegut de la clau API
  • Compliment: Compleix els requisits d’auditoria de seguretat
  • Control d’Accés: Permisos detallats i caducitat automàtica
  • Monitoratge: Millor seguiment de les fonts i patrons de pujada

vs. Altres Serveis de Pinning

IPFS.NINJA és actualment l’únic servei de pinning important que ofereix tokens de pujada signats. Competidors com Pinata requereixen proxy del costat del servidor o exposició de la clau API del costat del client, fent d’això un diferenciador únic.

Per a més detalls sobre com IPFS.NINJA es compara amb altres serveis, consulteu la nostra guia de comparació completa.

Consells per al Desplegament en Producció

Quan desplegeu tokens de pujada signats en producció:

Configuració de l’Entorn

Emmagatzemeu la configuració sensible de forma segura:

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

Monitoratge i Registre

Implementeu un registre complet per al monitoratge de seguretat:

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

Gestió d’Errors

Implementeu una gestió d’errors robusta que no filtri informació sensible:

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

Integració amb Frameworks Populars

Els tokens de pujada signats funcionen perfectament amb els frameworks web moderns. Aquí teniu exemples ràpids d’integració:

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

Conclusió

Els tokens de pujada signats resolen un repte de seguretat crític en el desenvolupament d’aplicacions descentralitzades. En proporcionar una manera segura de permetre pujades directes del costat del client a IPFS sense exposar les claus API, obren noves possibilitats arquitectòniques per a les aplicacions web modernes.

Tant si esteu construint un sistema de gestió de continguts, un marketplace NFT o qualsevol aplicació que requereixi pujades de fitxers segures, els tokens de pujada d’IPFS.NINJA proporcionen la seguretat i flexibilitat que necessiteu. La implementació és senzilla, els beneficis de seguretat són significatius i els guanys de rendiment són substancials.

Per aprendre més sobre els fonaments d’IPFS, consulteu la nostra guia sobre què és l’IPFS pinning o exploreu el nostre tutorial complet de l’API. Per als desenvolupadors que avaluen diferents opcions, la nostra comparació dels millors serveis de pinning IPFS proporciona informació completa.

Esteu a punt per implementar pujades IPFS segures del costat del client a la vostra aplicació? La combinació de pràctiques de seguretat modernes i emmagatzematge descentralitzat fa que aquesta aproximació sigui ideal per a aplicacions de producció que necessiten escalar mantenint els estàndards de seguretat.

Esteu a punt per començar el pinning? Creeu un compte gratuït --- 500 fitxers, 1 GB d’emmagatzematge, gateway dedicat. No cal targeta de crèdit.

Tornar al Blog

Articles Relacionats

Veure Tots els Articles »