Exporting files to SFTP servers via web browsers

Exporting files to SFTP servers directly from web browsers can be challenging because browsers do not natively support the SFTP protocol. In this DevTip, we explain a practical approach that bridges this gap by using JavaScript on the front end and Node.js on the back end to securely upload files to an SFTP server.
Introduction
Transferring files securely from web applications to remote servers via SFTP is a common requirement but not one that can be handled directly within a web browser. This guide provides a step-by-step process to implement SFTP file exports in your web application, addressing both front-end and back-end considerations.
Understanding the challenge
Web browsers are limited by security restrictions and the inherent nature of the SFTP protocol, which prevents direct communication with SFTP servers. By setting up a back-end service that manages the SFTP communication while the front end collects and sends the file data, we overcome these limitations effectively.
Setting up the frontend
Begin by creating a simple HTML page that enables users to select files for export:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>SFTP File Export</title>
</head>
<body>
<input type="file" id="fileInput" accept=".pdf,.jpg,.png,.doc,.docx" />
<button id="uploadButton">Export to SFTP</button>
<script>
const fileInput = document.getElementById('fileInput')
const uploadButton = document.getElementById('uploadButton')
const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB
uploadButton.addEventListener('click', async () => {
const file = fileInput.files[0]
if (!file) {
alert('Please select a file.')
return
}
if (file.size > MAX_FILE_SIZE) {
alert('File size exceeds 5MB limit.')
return
}
const formData = new FormData()
formData.append('file', file)
try {
const response = await fetch('/upload', {
method: 'POST',
body: formData,
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()
alert('File exported successfully.')
} catch (error) {
console.error('Error:', error)
alert('File export failed: ' + error.message)
}
})
</script>
</body>
</html>
Building the backend API with Node.js
We use Node.js for the back end due to its strong support for asynchronous operations and its rich ecosystem of packages for handling HTTP and SFTP protocols. The following steps demonstrate how to create a simple Express server that manages file uploads and performs SFTP transfers.
First, initialize a new Node.js project:
mkdir sftp-upload
cd sftp-upload
npm init -y
Install the required dependencies:
npm install express@4.18.x multer@1.4.5-lts.1 ssh2-sftp-client@11.0.0 dotenv@16.x express-rate-limit@7.x
Create an index.js
file:
require('dotenv').config()
const express = require('express')
const multer = require('multer')
const SftpClient = require('ssh2-sftp-client')
const path = require('path')
const fs = require('fs')
const rateLimit = require('express-rate-limit')
const app = express()
// Create uploads directory if it doesn't exist
if (!fs.existsSync('uploads')) {
fs.mkdirSync('uploads')
}
// Configure rate limiting for the upload endpoint
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
})
app.use('/upload', limiter)
// Configure multer with disk storage
const storage = multer.diskStorage({
destination: 'uploads/',
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
},
})
// Add file filter for security
const fileFilter = (req, file, cb) => {
const allowedTypes = [
'application/pdf',
'image/jpeg',
'image/png',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
]
if (allowedTypes.includes(file.mimetype)) {
cb(null, true)
} else {
cb(new Error('Invalid file type'), false)
}
}
const upload = multer({
storage: storage,
fileFilter: fileFilter,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB limit
},
})
app.post('/upload', upload.single('file'), async (req, res) => {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' })
}
const sftp = new SftpClient()
try {
if (!process.env.SFTP_HOST || !process.env.SFTP_USERNAME) {
throw new Error('Missing SFTP configuration')
}
await sftp.connect({
host: process.env.SFTP_HOST,
port: parseInt(process.env.SFTP_PORT || '22', 10),
username: process.env.SFTP_USERNAME,
password: process.env.SFTP_PASSWORD,
readyTimeout: 10000,
retries: 3,
})
const remotePath = `/remote/path/${req.file.filename}`
await sftp.put(req.file.path, remotePath)
// Clean up uploaded file
fs.unlinkSync(req.file.path)
res.json({ message: 'File exported successfully' })
} catch (err) {
console.error(err)
res.status(500).json({ error: 'File export failed: ' + err.message })
// Clean up uploaded file on error
if (req.file && fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path)
}
} finally {
sftp.end()
}
})
const port = process.env.PORT || 3000
app.listen(port, () => {
console.log(`Server started on http://localhost:${port}`)
})
Create a .env
file in the root of your project to store your SFTP credentials securely:
SFTP_HOST=your-sftp-host.com
SFTP_PORT=22
SFTP_USERNAME=your-username
SFTP_PASSWORD=your-password # Consider using key-based authentication for enhanced security
PORT=3000
Note: Never commit your .env
file to version control. Add it to your .gitignore
file.
Security considerations
A robust file transfer implementation requires strong security practices. Consider the following measures:
- File Validation: Rigorously check file types, enforce file size limits, and validate file names to prevent malicious uploads.
- Rate Limiting: Use tools such as
express-rate-limit
to restrict the number of requests, protecting your server from abuse. - SFTP Security:
- Use connection timeouts.
- Prefer key-based authentication over passwords when possible.
- Ensure that your SFTP server utilizes strong encryption.
- Error Handling and Cleanup: Implement comprehensive error handling, including cleanup of temporary files and secure logging of errors.
- Environment Variables: Store sensitive credentials in environment variables rather than hard-coding them.
- HTTPS: Serve your application over HTTPS to encrypt data in transit.
Testing the file export functionality
Start the Node.js server:
node index.js
Then, open the HTML file in your browser, select a file to export, and click the "Export to SFTP" button. If configured correctly, the file will be securely transferred to your SFTP server.
Conclusion
This guide demonstrates a secure and effective solution for exporting files to SFTP servers from web applications using JavaScript and Node.js. By addressing file validation, rate limiting, secure SFTP practices, and error handling, you can build a robust file export system.
For a more comprehensive solution that includes file processing and transformation, consider using Transloadit with its SFTP export Robot.