File Upload, Cookies & Sessions
Handle multipart uploads with multer, manage cookies, and build secure session-based auth
multer
Cookies
Sessions
Redis
1. File Upload
What it is: The process of accepting files (images, documents, videos, etc.) from clients and storing them on the server or cloud storage. File uploads require special handling for multipart form data, size limits, security validation, and storage management.
Why it matters
- User-generated content: Profile pictures, attachments, documents
- Data import: CSV/Excel uploads for bulk operations
- Rich media: Images, videos for social/media platforms
Security concerns
- Malicious files (executables, scripts)
- File size attacks (DoS via huge uploads)
- Path traversal (overwriting system files)
- MIME type spoofing (fake image containing PHP code)
Best practices
- Validate file type (magic numbers, not just extension)
- Scan for viruses (ClamAV, cloud scanners)
- Store files outside web root or use cloud storage
- Generate random filenames (never trust user-provided names)
- Set reasonable size limits
- Use streaming to avoid memory issues with large files
Short Example (Express + multer):
JavaScript — multer setupconst multer = require('multer');
const path = require('path');
const crypto = require('crypto');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'uploads/');
},
filename: (req, file, cb) => {
const uniqueSuffix = crypto.randomBytes(16).toString('hex');
cb(null, `${Date.now()}-${uniqueSuffix}${path.extname(file.originalname)}`);
}
});
const fileFilter = (req, file, cb) => {
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('Invalid file type. Only JPEG, PNG, GIF, and PDF allowed'), false);
}
};
const upload = multer({
storage: storage,
limits: { fileSize: 5 * 1024 * 1024 },
fileFilter: fileFilter
});
JavaScript — Upload routes// Single file upload
app.post('/upload/profile', upload.single('avatar'), (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
res.json({
message: 'File uploaded successfully',
file: {
originalName: req.file.originalname,
size: req.file.size,
path: req.file.path,
mimetype: req.file.mimetype
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Multiple files (array)
app.post('/upload/gallery', upload.array('photos', 10), (req, res) => {
res.json({
message: `${req.files.length} files uploaded`,
files: req.files.map(f => f.filename)
});
});
// Multiple fields
app.post('/upload/documents',
upload.fields([
{ name: 'resume', maxCount: 1 },
{ name: 'portfolio', maxCount: 5 }
]),
(req, res) => {
res.json({
resume: req.files['resume']?.[0]?.filename,
portfolio: req.files['portfolio']?.map(f => f.filename)
});
}
);
// Error handling for multer
app.use((error, req, res, next) => {
if (error instanceof multer.MulterError) {
if (error.code === 'FILE_TOO_LARGE') {
return res.status(400).json({ error: 'File too large (max 5MB)' });
}
if (error.code === 'LIMIT_FILE_COUNT') {
return res.status(400).json({ error: 'Too many files' });
}
}
next(error);
});
Stream to cloud storage (S3 example):
JavaScript — multer-s3const AWS = require('aws-sdk');
const multer = require('multer');
const multerS3 = require('multer-s3');
const s3 = new AWS.S3({ region: 'us-east-1' });
const upload = multer({
storage: multerS3({
s3: s3,
bucket: 'my-app-uploads',
acl: 'private',
key: (req, file, cb) => {
const filename = `${Date.now()}-${file.originalname}`;
cb(null, `users/${req.userId}/${filename}`);
},
metadata: (req, file, cb) => {
cb(null, { fieldName: file.fieldname, userId: req.userId });
}
}),
limits: { fileSize: 10 * 1024 * 1024 }
});
app.post('/upload/secure', upload.single('file'), (req, res) => {
res.json({ url: req.file.location });
});
2. Cookies
What they are: Small pieces of data (key-value pairs) stored on the client's browser by a website. Cookies are sent automatically with every HTTP request to the same domain, making them useful for maintaining state, tracking, and storing preferences.
Why they matter
- Session management: Login sessions, shopping carts
- Personalization: User preferences, themes, language
- Tracking: Analytics, user behavior
Cookie attributes (security & behavior)
- HttpOnly → Inaccessible to JavaScript (prevents XSS theft)
- Secure → Only sent over HTTPS
- SameSite → Controls cross-site sending (CSRF protection)
Strict: Only for same-site requests
Lax: Sent for top-level navigation (default in modern browsers)
None: Sent everywhere (requires Secure)
- Domain → Which domains can receive the cookie
- Path → URL path scope
- Expires / Max-Age → When cookie expires (session vs persistent)
Cookie sizes: Max 4KB per cookie, ~20 cookies per domain.
Short Example (Express + cookie-parser):
JavaScript — cookie-parserconst cookieParser = require('cookie-parser');
app.use(cookieParser());
// Setting cookies
app.post('/login', (req, res) => {
res.cookie('sessionId', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
res.cookie('theme', 'dark', {
maxAge: 7 * 24 * 60 * 60 * 1000,
httpOnly: false,
sameSite: 'lax'
});
res.cookie('userId', '123', { signed: true });
res.json({ message: 'Logged in' });
});
// Reading cookies
app.get('/profile', (req, res) => {
const theme = req.cookies.theme || 'light';
const sessionId = req.cookies.sessionId;
res.json({ theme, sessionId });
});
// Deleting cookies
app.post('/logout', (req, res) => {
res.clearCookie('sessionId');
res.clearCookie('theme');
res.json({ message: 'Logged out' });
});
JavaScript — Cookie optionsconst cookieOptions = {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000,
domain: '.example.com'
};
res.cookie('preference', 'value', cookieOptions);
Working with cookies in browser (JavaScript):
JavaScript — Browser cookiesdocument.cookie = "theme=dark; max-age=604800; path=/";
const cookies = document.cookie.split('; ').reduce((acc, cookie) => {
const [key, value] = cookie.split('=');
acc[key] = value;
return acc;
}, {});
document.cookie = "theme=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/";
Security example — Preventing CSRF with SameSite:
JavaScript// Vulnerable to CSRF without proper settings
res.cookie('authToken', token, { httpOnly: true });
// Protected against CSRF
res.cookie('authToken', token, {
httpOnly: true,
sameSite: 'strict',
secure: true
});
// For cross-site use cases (e.g., embedded widgets)
res.cookie('analyticsId', id, {
sameSite: 'none',
secure: true
});
3. Sessions (Deep Dive)
What they are: Server-side storage of user data identified by a unique session ID. Unlike JWTs (client-side), session data never leaves the server. The client only stores a session ID (usually in a cookie), which acts as a key to retrieve server-side data.
How sessions work
- User logs in → Server creates session object with user data in storage (memory, Redis, DB)
- Server generates unique session ID and sends it to client as a cookie
- Client sends session ID cookie on every request
- Server looks up session data using that ID
- Session expires after inactivity or logout → server deletes it
Session vs JWT comparison
| Aspect | Sessions | JWT |
| Storage | Server-side | Client-side |
| Revocation | Instant (delete session) | Hard (must maintain blacklist) |
| Scalability | Needs shared session store | Stateless, any server |
| Payload size | Small cookie | Larger token |
| Security | Easier to secure | Must handle token storage carefully |
Session storage options
- In-memory (default): Fast but not scalable (lost on server restart)
- Redis: Fast, persistent, shared across servers
- Database: Slow but reliable for low-traffic apps
Short Example (Express + express-session with Redis):
JavaScript — Redis session setupconst session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('redis');
const redisClient = redis.createClient({
host: 'localhost',
port: 6379,
password: process.env.REDIS_PASSWORD
});
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: 'your-strong-secret-key',
resave: false,
saveUninitialized: false,
name: 'sessionId',
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 1000 * 60 * 60 * 24
}
}));
JavaScript — Login, profile, logoutapp.post('/api/login', async (req, res) => {
const { email, password } = req.body;
const user = await authenticateUser(email, password);
if (user) {
req.session.userId = user.id;
req.session.email = user.email;
req.session.role = user.role;
req.session.loginTime = Date.now();
await db.saveSession(user.id, req.sessionID);
res.json({ message: 'Logged in successfully', user: { email: user.email, role: user.role } });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
app.get('/api/profile', async (req, res) => {
if (!req.session.userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
res.json({
userId: req.session.userId,
email: req.session.email,
role: req.session.role,
sessionId: req.sessionID,
loggedInDuration: Date.now() - req.session.loginTime
});
});
function requireRole(role) {
return (req, res, next) => {
if (!req.session.userId) {
return res.status(401).json({ error: 'Authentication required' });
}
if (req.session.role !== role) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
app.delete('/api/admin/users/:id', requireRole('admin'), async (req, res) => {
await db.deleteUser(req.params.id);
res.json({ message: 'User deleted' });
});
app.post('/api/logout', async (req, res) => {
req.session.destroy((err) => {
if (err) return res.status(500).json({ error: 'Logout failed' });
res.clearCookie('sessionId');
res.json({ message: 'Logged out successfully' });
});
});
Advanced session patterns
JavaScript// Regenerate session ID (prevents session fixation)
app.post('/login', async (req, res) => {
const user = await authenticate(req.body);
req.session.regenerate((err) => {
if (err) return res.status(500).json({ error: 'Session error' });
req.session.userId = user.id;
res.json({ message: 'Logged in' });
});
});
// Touch session (reset expiration)
app.post('/api/checkout', async (req, res) => {
req.session.touch();
res.json({ message: 'Order placed' });
});
// Session heartbeat
app.get('/api/heartbeat', (req, res) => {
if (req.session.userId) {
res.json({ active: true, userId: req.session.userId });
} else {
res.json({ active: false });
}
});
Memory store (development only):
JavaScript — Dev only// NOT for production (doesn't scale, lost on restart)
app.use(session({
secret: 'dev-secret',
resave: false,
saveUninitialized: true,
cookie: { secure: false }
}));
Session security checklist
- Use
httpOnly to prevent XSS access
- Use
secure flag in production (HTTPS only)
- Use
sameSite: 'strict' to prevent CSRF
- Regenerate session ID after login (prevents fixation)
- Set reasonable timeout (balance security vs UX)
- Use Redis/DB store for multi-server deployments
- Never store sensitive data in session (credit cards, passwords)
Combined Example: File Upload with Session Authentication
JavaScriptconst upload = multer({ storage, limits, fileFilter });
function requireAuth(req, res, next) {
if (!req.session.userId) {
return res.status(401).json({ error: 'Please login first' });
}
next();
}
app.post('/upload/avatar', requireAuth, upload.single('avatar'), async (req, res) => {
try {
const userId = req.session.userId;
const filePath = req.file.path;
await db.updateUser(userId, { avatar: filePath });
req.session.avatarUrl = `/uploads/${req.file.filename}`;
res.cookie('avatarUpdated', Date.now(), { maxAge: 86400000 });
res.json({
message: 'Avatar uploaded',
path: req.session.avatarUrl
});
} catch (error) {
res.status(500).json({ error: 'Upload failed' });
}
});
app.get('/dashboard', requireAuth, (req, res) => {
res.json({
user: {
id: req.session.userId,
email: req.session.email,
avatar: req.session.avatarUrl
},
preferences: req.cookies
});
});
Summary Table
| Concept |
Storage Location |
Size Limit |
Security Features |
Main Use Case |
| File Upload |
Server/Cloud |
Configurable (MB-GB) |
Type validation, virus scan, random names |
User content, media |
| Cookies |
Client (browser) |
4KB |
HttpOnly, Secure, SameSite |
Preferences, tracking |
| Sessions |
Server (RAM/Redis/DB) |
Server-dependent |
HttpOnly cookie + server control |
Authentication, state |
Quick Decision Guide
- File uploads → Always validate on server, never trust client extensions
- Cookies → For small, non-sensitive data that needs persistence
- Sessions → For authentication and server-side state that must be revocable
- JWT → For stateless APIs where instant revocation isn't critical