MPA Security — Protecting Multi-Page Applications from Vulnerabilities
In this tutorial, you will learn about MPA Security. We cover key concepts, practical examples, and best practices to help you master this topic.
MPA security covers server-side input validation, SQL Injection prevention, XSS protection, CSRF token implementation, secure session management, HTTPS enforcement, and security headers for multi-page applications.
What You'll Learn
By the end of this tutorial, you will understand the main security threats facing MPAs, how to prevent SQL injection with parameterized queries, protect against XSS with output encoding, implement CSRF tokens, secure sessions, enforce HTTPS, configure security headers, and follow OWASP guidelines for web applications.
Why It Matters
MPAs handle sensitive data on the server — user credentials, payment information, personal data. A single vulnerability can expose millions of records. Security is not optional; it is a fundamental requirement. Understanding and implementing security measures protects your users and your business from data breaches, legal liability, and reputational damage.
Real-World Use
A healthcare MPA suffered a SQL injection attack that exposed 500,000 patient records including medical histories and social security numbers. The vulnerability was in a search feature that concatenated user input directly into SQL queries. After switching to parameterized queries and implementing input validation, no further SQL injection attacks succeeded.
OWASP Top 10 for MPAs
┌──────────────────────────────────────────────────────────┐
│ OWASP Top 10 — MPA Focus │
├──────────────────────────────────────────────────────────┤
│ │
│ 1. Broken Access Control → Server-side auth checks │
│ 2. Cryptographic Failures → HTTPS, secure cookies │
│ 3. Injection (SQL, NoSQL) → Parameterized queries │
│ 4. Insecure Design → Security by design │
│ 5. Security Misconfiguration → Headers, CORS, CSP │
│ 6. Vulnerable Components → Dependency updates │
│ 7. Auth Failures → Strong password policy │
│ 8. Data Integrity Failures → CSRF tokens │
│ 9. Logging Failures → Audit logging │
│ 10. SSRF → URL validation │
│ │
└──────────────────────────────────────────────────────────┘
Think of MPA security like securing a building. Input validation is the receptionist checking IDs at the entrance. Parameterized queries are the secure internal mail system that prevents packages from being opened mid-delivery. Security headers are the security cameras. HTTPS is the encrypted tunnel connecting the building to the outside world.
SQL Injection Prevention
// BAD — vulnerable to SQL injection
app.get('/products', (req, res) => {
const category = req.query.category;
// Never do this — concatenating user input into SQL
const query = `SELECT * FROM products WHERE category = '${category}'`;
// If category = "'; DROP TABLE products; --"
// The query becomes: SELECT * FROM products WHERE category = ''; DROP TABLE products; --'
db.query(query, (err, results) => {
res.render('products', { products: results });
});
});
// GOOD — parameterized query
app.get('/products', (req, res) => {
const category = req.query.category;
// Parameterized query — user input is separated from SQL
db.query(
'SELECT * FROM products WHERE category = $1',
[category], // Parameters are escaped automatically
(err, results) => {
if (err) {
console.error('Database error:', err);
return res.status(500).render('error', {
message: 'An error occurred. Please try again.'
});
}
res.render('products', { products: results });
}
);
});
// Additional input validation
function validateCategory(category) {
const validCategories = ['electronics', 'clothing', 'food', 'books'];
if (!validCategories.includes(category)) {
return null;
}
return category;
}
Security Headers Configuration
// Security headers middleware
const helmet = require('helmet');
app.use(helmet({
// Content Security Policy
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'https:', 'data:'],
connectSrc: ["'self'", 'https://api.example.com'],
fontSrc: ["'self'", 'https://fonts.gstatic.com'],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
formAction: ["'self'"]
}
},
// HTTP Strict Transport Security
hsts: {
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true
},
// Referrer Policy
referrerPolicy: { policy: 'strict-origin-when-cross-origin' }
}));
// Resulting response headers:
// Content-Security-Policy: default-src 'self'; ...
// Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
// X-Content-Type-Options: nosniff
// X-Frame-Options: DENY
// X-XSS-Protection: 0 (modern browsers disable this)
// Referrer-Policy: strict-origin-when-cross-origin
Output Encoding for XSS Prevention
<!-- EJS template with proper output encoding -->
<!-- Escaped output (recommended) — prevents XSS -->
<h1><%= product.name %></h1>
<!-- If product.name = "<script>alert('xss')</script>"
Output: <script>alert('xss')</script>
Safe! -->
<!-- Unescaped output — use only with trusted content -->
<div><%- product.sanitizedDescription %></div>
<!-- Use DOMPurify on the server before passing to template -->
<!-- URL encoding -->
<a href="/products/<%= encodeURIComponent(product.slug) %>">
<%= product.name %>
</a>
<!-- JavaScript context — JSON encode -->
<script>
var product = <%- JSON.stringify(product) %>;
// JSON.stringify escapes special characters
// Never use user input directly in <script> tags
</script>
<!-- HTML attribute encoding -->
<input type="text"
value="<%= product.name.replace(/"/g, '"') %>">
Common Mistakes
- SQL injection through unprotected inputs. Every user input that reaches a database query must use parameterized queries or prepared statements. Never concatenate input into SQL strings.
- Missing security headers. Without CSP, HSTS, X-Frame-Options, and other headers, your application is vulnerable to clickjacking, MIME-type sniffing, and content injection.
- Server-side validation gaps. Client-side validation is for UX; server-side validation is for security. Always validate and sanitize input on the server.
- Insecure session configuration. Sessions without HttpOnly, Secure, and SameSite flags are vulnerable to theft. Use a secure session store (Redis) with proper configuration.
- Exposing stack traces in production. Error messages should be user-friendly, not technical. Never reveal database schemas, file paths, or dependency versions in production error pages.
Practice Questions
- How does SQL injection work and how do you prevent it?
- What security headers should every MPA include?
- What is the difference between escaped (<%= %>) and unescaped (<%- %>) output in templates?
- Why is server-side validation necessary even with client-side validation?
- What session configuration settings improve security?
Challenge: Perform a security audit on an MPA using OWASP ZAP scanner. Check for: SQL injection vulnerabilities, missing security headers, XSS vulnerabilities, CSRF protection, insecure session configuration, exposed error pages, and outdated dependencies. Fix all high and medium severity issues.
FAQ
Mini Project
Secure an MPA with: SQL injection prevention using parameterized queries for all database operations, Helmet.js security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options), CSRF tokens on all forms, secure session configuration (HttpOnly, Secure, SameSite), output encoding in all templates, input validation and sanitization, Rate Limiting on login endpoints, and HTTPS enforcement.
What's Next
You understand MPA security. Now build a complete MPA mini project that combines everything you have learned.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro