File Upload, Cookies & Sessions

Handle multipart uploads with multer, manage cookies, and build secure session-based auth

multer Cookies Sessions Redis

Table of Contents

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

  1. User logs in → Server creates session object with user data in storage (memory, Redis, DB)
  2. Server generates unique session ID and sends it to client as a cookie
  3. Client sends session ID cookie on every request
  4. Server looks up session data using that ID
  5. Session expires after inactivity or logout → server deletes it

Session vs JWT comparison

AspectSessionsJWT
StorageServer-sideClient-side
RevocationInstant (delete session)Hard (must maintain blacklist)
ScalabilityNeeds shared session storeStateless, any server
Payload sizeSmall cookieLarger token
SecurityEasier to secureMust 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