· Nacho Coll · Guides · 11 min de leitura
Tokens de Upload IPFS: Uploads Seguros do Lado do Cliente sem Expor Chaves API
Aprenda como tokens de upload assinados permitem que você faça uploads para o IPFS com segurança a partir de navegadores e aplicativos móveis sem expor sua chave API.

Construir aplicações web modernas frequentemente requer o upload de arquivos diretamente dos navegadores dos usuários para o armazenamento em nuvem. No entanto, quando se trata da segurança de uploads IPFS, os desenvolvedores enfrentam um dilema desafiador: como permitir uploads do lado do cliente sem expor suas valiosas chaves API a possíveis abusos?
A maioria dos serviços de pinning IPFS força você a uma escolha desconfortável: lidar com todos os uploads do lado do servidor (criando gargalos e complexidade) ou embutir sua chave API no código do cliente (um pesadelo de segurança). O IPFS.NINJA resolve isso com um recurso exclusivo que nenhum outro serviço de pinning oferece: tokens de upload assinados.

O Problema com Chaves API Tradicionais do IPFS
Ao construir aplicações do lado do cliente que precisam fazer upload para o IPFS, os desenvolvedores tipicamente encontram vários desafios de segurança:
Risco de Exposição da Chave API
Embutir chaves API diretamente no JavaScript do navegador significa que qualquer pessoa pode visualizar seu código-fonte e extrair suas credenciais. Isso pode levar a:
- Uploads não autorizados consumindo sua cota de armazenamento
- Abuso potencial da sua conta no serviço de pinning
- Violações de conformidade de segurança em ambientes corporativos
Gargalos do Lado do Servidor
A alternativa --- rotear todos os uploads pelo seu backend --- cria vários problemas:
- Custos maiores de largura de banda do servidor
- Maior latência para os usuários
- Requisitos de infraestrutura mais complexos
- Possíveis pontos únicos de falha
Segurança em Aplicativos Móveis
Aplicativos móveis enfrentam desafios semelhantes, onde chaves API armazenadas nos pacotes do aplicativo podem ser extraídas por engenharia reversa.
Apresentando os Tokens de Upload IPFS
Os tokens de upload assinados do IPFS.NINJA fornecem um meio-termo seguro. Veja como funcionam:
- O servidor gera o token: Seu backend cria um token assinado com tempo limitado usando sua chave API
- O cliente recebe o token: O token é transmitido com segurança para sua aplicação frontend
- Upload direto: Os clientes fazem upload diretamente para o IPFS.NINJA usando o token assinado
- Expiração automática: Os tokens expiram após uma duração definida, limitando a janela de exposição
Essa abordagem combina a segurança da autenticação do lado do servidor com os benefícios de desempenho dos uploads diretos do cliente.
Entendendo a Segurança dos Tokens de Upload
Os tokens de upload assinados utilizam assinaturas criptográficas para garantir a autenticidade sem expor sua chave API principal. Cada token contém:
- Timestamp de expiração: Invalidação automática após a duração especificada
- Restrições de uso: Limites opcionais na quantidade de arquivos ou tamanho total
- Assinatura criptográfica: Previne adulteração ou falsificação
- Verificação do emissor: Vincula-se à sua conta autenticada
Diferente das chaves API, os tokens de upload são projetados para serem embutidos com segurança no código do lado do cliente. Mesmo se extraídos, eles fornecem acesso limitado que expira automaticamente.
Implementação do Backend: Exemplo com Express.js
Vamos construir um exemplo completo mostrando como implementar uploads seguros para o IPFS do lado do cliente. Primeiro, aqui está o backend Express.js que gera tokens de upload:
// 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');
});Implementação do Frontend: Upload Seguro do Cliente
Agora, aqui está o código do frontend que faz upload de arquivos com segurança usando o token assinado:
<!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>Considerações Avançadas de Segurança
Ao implementar segurança de upload IPFS com tokens assinados, considere estas medidas de segurança adicionais:
Limitações de Escopo do Token
Configure tokens com restrições apropriadas:
// 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
})
});Validação de Conteúdo
Sempre valide o conteúdo enviado no seu backend:
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' });
}
});Limitação de Taxa
Implemente limitação de taxa adicional no seu endpoint de geração 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);Benefícios Sobre as Abordagens Tradicionais
Os tokens de upload assinados oferecem várias vantagens sobre os métodos alternativos de segurança de upload IPFS:
vs. Proxy do Lado do Servidor
- Desempenho: Uploads diretos eliminam o uso de largura de banda do servidor
- Escalabilidade: Sem gargalos do servidor durante períodos de alta demanda de uploads
- Custo: Redução de custos de largura de banda e processamento
- Experiência do Usuário: Melhores velocidades de upload e acompanhamento de progresso
vs. Chaves API no Cliente
- Segurança: Sem risco de extração ou uso indevido de chaves API
- Conformidade: Atende aos requisitos de auditoria de segurança
- Controle de Acesso: Permissões granulares e expiração automática
- Monitoramento: Melhor rastreamento das fontes e padrões de upload
vs. Outros Serviços de Pinning
O IPFS.NINJA é atualmente o único serviço de pinning importante que oferece tokens de upload assinados. Concorrentes como o Pinata exigem proxy do lado do servidor ou exposição de chaves API no cliente, tornando isso um diferencial único.
Para mais detalhes sobre como o IPFS.NINJA se compara a outros serviços, confira nosso guia de comparação completo.
Dicas para Deploy em Produção
Ao fazer deploy de tokens de upload assinados em produção:
Configuração do Ambiente
Armazene configurações sensíveis com segurança:
// 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']
};Monitoramento e Logging
Implemente logging abrangente para monitoramento de segurança:
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
});Tratamento de Erros
Implemente tratamento de erros robusto que não vaze informações sensíveis:
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.'
});
});Integração com Frameworks Populares
Os tokens de upload assinados funcionam perfeitamente com frameworks web modernos. Aqui estão exemplos rápidos de integração:
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
};
}Conclusão
Os tokens de upload assinados resolvem um desafio de segurança crítico no desenvolvimento de aplicações descentralizadas. Ao fornecer uma forma segura de habilitar uploads diretos do lado do cliente para o IPFS sem expor chaves API, eles abrem novas possibilidades arquiteturais para aplicações web modernas.
Seja construindo um sistema de gerenciamento de conteúdo, um marketplace de NFT ou qualquer aplicação que exija uploads seguros de arquivos, os tokens de upload do IPFS.NINJA fornecem a segurança e a flexibilidade que você precisa. A implementação é direta, os benefícios de segurança são significativos e os ganhos de desempenho são substanciais.
Para aprender mais sobre os fundamentos do IPFS, confira nosso guia sobre o que é pinning no IPFS ou explore nosso tutorial completo da API. Para desenvolvedores avaliando diferentes opções, nossa comparação dos melhores serviços de pinning IPFS oferece insights abrangentes.
Pronto para implementar uploads seguros do lado do cliente para o IPFS na sua aplicação? A combinação de práticas modernas de segurança e armazenamento descentralizado torna essa abordagem ideal para aplicações em produção que precisam escalar mantendo padrões de segurança.
Pronto para começar a fazer pinning? Crie uma conta gratuita --- 500 arquivos, 1 GB de armazenamento, gateway dedicado. Sem necessidade de cartão de crédito.
