· 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 envie ficheiros para o IPFS com segurança a partir de navegadores e aplicações móveis sem expor a sua chave API.

Construir aplicações web modernas requer frequentemente o upload de ficheiros diretamente dos navegadores dos utilizadores para o armazenamento na nuvem. No entanto, quando se trata da segurança de uploads IPFS, os programadores enfrentam um dilema desafiante: como permitir uploads do lado do cliente sem expor as suas valiosas chaves API a possíveis abusos?
A maioria dos serviços de pinning IPFS obriga-o a uma escolha desconfortável: tratar todos os uploads do lado do servidor (criando estrangulamentos e complexidade) ou incorporar a sua chave API no código do cliente (um pesadelo de segurança). O IPFS.NINJA resolve isto com uma funcionalidade exclusiva que nenhum outro serviço de pinning oferece: tokens de upload assinados.

O Problema com as Chaves API Tradicionais do IPFS
Ao construir aplicações do lado do cliente que precisam de fazer upload para o IPFS, os programadores tipicamente encontram vários desafios de segurança:
Risco de Exposição da Chave API
Incorporar chaves API diretamente no JavaScript do navegador significa que qualquer pessoa pode ver o seu código-fonte e extrair as suas credenciais. Isto pode levar a:
- Uploads não autorizados a consumir a sua quota de armazenamento
- Potencial abuso da sua conta no serviço de pinning
- Violações de conformidade de segurança em ambientes empresariais
Estrangulamentos do Lado do Servidor
A alternativa --- encaminhar todos os uploads pelo seu backend --- cria vários problemas:
- Custos mais elevados de largura de banda do servidor
- Maior latência para os utilizadores
- Requisitos de infraestrutura mais complexos
- Possíveis pontos únicos de falha
Segurança em Aplicações Móveis
As aplicações móveis enfrentam desafios semelhantes, onde chaves API armazenadas nos pacotes da aplicação podem ser extraídas por engenharia reversa.
Apresentamos os Tokens de Upload IPFS
Os tokens de upload assinados do IPFS.NINJA proporcionam um meio-termo seguro. Eis como funcionam:
- O servidor gera o token: O seu backend cria um token assinado com tempo limitado utilizando a sua chave API
- O cliente recebe o token: O token é transmitido com segurança para a sua aplicação frontend
- Upload direto: Os clientes fazem upload diretamente para o IPFS.NINJA utilizando o token assinado
- Expiração automática: Os tokens expiram após uma duração definida, limitando a janela de exposição
Esta abordagem combina a segurança da autenticação do lado do servidor com os benefícios de desempenho dos uploads diretos do cliente.
Compreender a Segurança dos Tokens de Upload
Os tokens de upload assinados utilizam assinaturas criptográficas para garantir a autenticidade sem expor a sua chave API principal. Cada token contém:
- Marca temporal de expiração: Invalidação automática após a duração especificada
- Restrições de utilização: Limites opcionais no número de ficheiros ou tamanho total
- Assinatura criptográfica: Previne adulteração ou falsificação
- Verificação do emissor: Liga-se à sua conta autenticada
Ao contrário das chaves API, os tokens de upload foram concebidos para serem incorporados com segurança no código do lado do cliente. Mesmo se extraídos, proporcionam acesso limitado que expira automaticamente.
Implementação do Backend: Exemplo com Express.js
Vamos construir um exemplo completo que mostra como implementar uploads seguros para o IPFS do lado do cliente. Primeiro, eis 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, eis o código do frontend que faz upload de ficheiros com segurança utilizando 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 Âmbito 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
Valide sempre 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 Face às Abordagens Tradicionais
Os tokens de upload assinados proporcionam várias vantagens sobre os métodos alternativos de segurança de upload IPFS:
vs. Proxy do Lado do Servidor
- Desempenho: Uploads diretos eliminam a utilização de largura de banda do servidor
- Escalabilidade: Sem estrangulamentos do servidor durante períodos de alta procura de uploads
- Custo: Redução de custos de largura de banda e processamento
- Experiência do Utilizador: Melhores velocidades de upload e acompanhamento de progresso
vs. Chaves API no Cliente
- Segurança: Sem risco de extração ou utilização indevida de chaves API
- Conformidade: Cumpre os requisitos de auditoria de segurança
- Controlo de Acesso: Permissões granulares e expiração automática
- Monitorização: 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 isto um diferencial único.
Para mais detalhes sobre como o IPFS.NINJA se compara a outros serviços, consulte o 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']
};Monitorização e Registo
Implemente registo abrangente para monitorização 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 divulgue 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. Eis 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 proporcionar uma forma segura de permitir uploads diretos do lado do cliente para o IPFS sem expor chaves API, abrem novas possibilidades arquiteturais para aplicações web modernas.
Quer esteja a construir um sistema de gestão de conteúdo, um marketplace de NFT ou qualquer aplicação que exija uploads seguros de ficheiros, os tokens de upload do IPFS.NINJA proporcionam a segurança e a flexibilidade de que necessita. A implementação é simples, os benefícios de segurança são significativos e os ganhos de desempenho são substanciais.
Para saber mais sobre os fundamentos do IPFS, consulte o nosso guia sobre o que é pinning no IPFS ou explore o nosso tutorial completo da API. Para programadores a avaliar diferentes opções, a nossa comparação dos melhores serviços de pinning IPFS oferece informações 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 esta abordagem ideal para aplicações em produção que precisam de escalar mantendo padrões de segurança.
Pronto para começar a fazer pinning? Crie uma conta gratuita --- 500 ficheiros, 1 GB de armazenamento, gateway dedicado. Sem necessidade de cartão de crédito.
