Web Application Security: OWASP Top 10 in Practice

Web application security is not a topic anyone building software for the internet can afford to ignore. Every year, thousands of security incidents are disclosed -- from personal data breaches to complete system compromises. Most of them could have been prevented by following basic security principles.
OWASP (Open Web Application Security Project) publishes a list of the ten most critical security risks for web applications. The current 2021 version reflects the state of modern threats. In this article, we will walk through each risk with a real-world example and practical prevention strategies using modern frameworks like Next.js, React, and Node.js.
A01: Broken Access Control
Access control determines what each user is allowed to do. When it fails, users can access data or functions they are not authorized to use.
Real-World Example
A user changes the ID in the URL from /api/orders/123 to /api/orders/124 and gains access to another user's order. This is called IDOR (Insecure Direct Object Reference) and is one of the most common vulnerabilities.
Prevention
// Bad -- only checks if user is logged in
app.get('/api/orders/:id', authMiddleware, async (req, res) => {
const order = await Order.findById(req.params.id);
res.json(order);
});
// Good -- verifies the order belongs to the logged-in user
app.get('/api/orders/:id', authMiddleware, async (req, res) => {
const order = await Order.findOne({
_id: req.params.id,
userId: req.user.id
});
if (!order) return res.status(404).json({ error: 'Not found' });
res.json(order);
});
Principles: Always verify on the server that the user has the right to the requested operation. Never trust client-side data. Use a deny-by-default approach -- everything is forbidden unless explicitly allowed.
A02: Cryptographic Failures
Previously known as "Sensitive Data Exposure." This covers inadequate protection of sensitive data -- passwords in plain text, unencrypted communication, weak hashing algorithms.
Real-World Example
A database stores passwords hashed with MD5 without salt. After the database leaks, all passwords are cracked within hours.
Prevention
- Passwords: Use bcrypt or Argon2 with sufficient cost factor
- Communication: Always HTTPS, set HSTS headers
- Sensitive data: Encryption at rest (AES-256), minimize storage of sensitive data
- Tokens: Sign JWTs with strong keys, set expiration
// Password hashing with bcrypt in Node.js
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12;
async function hashPassword(password) {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password, hash) {
return bcrypt.compare(password, hash);
}
A03: Injection
SQL injection, NoSQL injection, command injection -- all variants work on the same principle: unsanitized user input is interpreted as code or a command.
Real-World Example
A login form where the username admin' OR '1'='1 bypasses authentication because the input is directly embedded in the SQL query.
Prevention
Parameterized queries are the only reliable solution for SQL injection:
// Bad -- directly embedded input (SQL injection)
const query = `SELECT * FROM users WHERE email = '${email}'`;
// Good -- parameterized query
const query = 'SELECT * FROM users WHERE email = $1';
const result = await pool.query(query, [email]);
In modern ORMs like Prisma or TypeORM, parameterization is automatic, but beware of raw queries that bypass these protections.
For NoSQL databases (MongoDB): Validate input types, use the $eq operator explicitly, and never pass user input directly into query operators.
A04: Insecure Design
This is a new category in OWASP 2021, focusing on flaws in design rather than implementation. No amount of security patches can save a poorly designed system.
Real-World Example
An e-shop allows unlimited attempts at entering a discount code. An attacker brute-forces millions of combinations and discovers valid codes.
Prevention
- Threat modeling at the start of the project -- identify what can go wrong
- Rate limiting on all critical endpoints
- Business logic must have security guardrails (maximum attempts, time limits, state verification)
- Principle of least privilege -- each component has only the permissions it actually needs
A05: Security Misconfiguration
The most common vulnerability in practice. Includes default passwords, open cloud storage buckets, unnecessarily enabled services, and verbose error messages in production.
Real-World Example
A Next.js application left with DEBUG=true that shows users the complete stack trace, including file paths and database table names.
Prevention
// next.config.js -- secure configuration
const nextConfig = {
poweredBy: false, // Remove X-Powered-By header
headers: async () => [
{
source: '/(.*)',
headers: [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=()' },
],
},
],
};
Additional measures: Regularly update dependencies, remove unnecessary features and endpoints, use different configurations for development and production, automate configuration checks.
A06: Vulnerable and Outdated Components
Modern applications use dozens to hundreds of third-party libraries. Each is a potential source of vulnerabilities.
Real-World Example
The log4j library in 2021 (Log4Shell) -- a critical vulnerability in one of the most widely used Java libraries affected millions of applications worldwide.
Prevention
# Regular vulnerability check in npm dependencies
npm audit
# Automatic fix for known vulnerabilities
npm audit fix
# Continuous monitoring tool
npx snyk test
- Automate checks using GitHub Dependabot or Snyk
- Update regularly -- enable automatic PRs with updates
- Remove unused dependencies -- every extra library is a potential risk
- Lock versions in production with lock files (package-lock.json)
A07: Identification and Authentication Failures
Covers weak passwords, missing multi-factor authentication, and improper session management.
Real-World Example
An application allows unlimited login attempts without any restrictions. An attacker uses credential stuffing (leaked password databases) and gains access to accounts.
Prevention
- Implement rate limiting on login endpoints (e.g., 5 attempts per 15 minutes)
- Require strong passwords (minimum 8 characters, check against leaked password databases)
- Support MFA (multi-factor authentication), ideally mandatory for administrators
- Secure session management: httpOnly cookies, secure flag, SameSite attribute, reasonable expiration
// Secure session cookie configuration
const sessionConfig = {
httpOnly: true, // Not accessible from JavaScript
secure: true, // HTTPS only
sameSite: 'strict', // CSRF protection
maxAge: 24 * 60 * 60 * 1000, // 24 hours
path: '/',
};
A08: Software and Data Integrity Failures
Covers situations where an application relies on untrusted sources -- unsecured CI/CD pipelines, unverified updates, deserialization of untrusted data.
Real-World Example
The SolarWinds attack in 2020 -- attackers compromised the build pipeline and injected malware into a legitimate update that was distributed to thousands of organizations.
Prevention
- Verify dependency integrity using checksums (lock files, Subresource Integrity for CDN)
- Secure your CI/CD pipeline -- minimal permissions, audit logs, environment separation
- Never deserialize untrusted data without validation
- Code review for all changes before merging to the main branch
A09: Security Logging and Monitoring Failures
Without proper logging and monitoring, you have no chance of discovering that your system has been compromised. The average time to detect a breach is over 200 days.
Real-World Example
An attacker gradually exfiltrates data over six months. Nobody notices because the application does not log failed login attempts, access to sensitive data, or unusual behavior patterns.
Prevention
- Log all security-relevant events: logins (successful and failed), permission changes, access to sensitive data, validation errors
- Never log sensitive data: passwords, tokens, and personal information do not belong in logs
- Set up alerting: automatic notifications for suspicious activity (e.g., 100 failed logins per minute)
- Centralize logs and protect them from tampering
// Security event logging example
function logSecurityEvent(event) {
const logEntry = {
timestamp: new Date().toISOString(),
type: event.type, // 'auth_failure', 'access_denied', 'rate_limit'
userId: event.userId,
ip: event.ip,
userAgent: event.userAgent,
resource: event.resource,
details: event.details,
};
// Send to centralized logging service
securityLogger.warn(logEntry);
}
A10: Server-Side Request Forgery (SSRF)
SSRF allows an attacker to make the server send an HTTP request to an arbitrary address -- including internal services that are not accessible from the internet.
Real-World Example
An "import from URL" feature allows users to enter an image URL. An attacker enters http://169.254.169.254/latest/meta-data/ and obtains AWS metadata including access keys.
Prevention
- Validate and sanitize all user-provided URLs
- Use an allowlist of permitted domains and protocols
- Block private IP ranges (10.x.x.x, 172.16-31.x.x, 192.168.x.x, 169.254.x.x)
- Do not pass raw responses back to the user
import { URL } from 'url';
function isUrlSafe(input) {
try {
const url = new URL(input);
// Allow only HTTPS
if (url.protocol !== 'https:') return false;
// Block private IPs
const hostname = url.hostname;
if (hostname === 'localhost' || hostname === '127.0.0.1') return false;
if (hostname.startsWith('10.')) return false;
if (hostname.startsWith('192.168.')) return false;
if (hostname.startsWith('169.254.')) return false;
// Additional checks...
return true;
} catch {
return false;
}
}
Security as a Process, Not a One-Time Task
The most important takeaway from the OWASP Top 10 is that security is not something you solve with a single audit or tool. It is a continuous process:
- Threat modeling during design -- identify risks before development begins
- Secure coding standards -- the entire team must know and follow basic rules
- Automated testing -- SAST (static analysis), DAST (dynamic testing), dependency scanning
- Security-focused code review -- every PR should be reviewed for security aspects
- Penetration testing -- regular testing by an external team
- Incident response plan -- know what to do when something happens
Build a security-first development culture. Security must not be a task left for the end of the project — it has to be part of every code review, every design, and every sprint. Start with threat modeling during design, automate security tests in CI/CD, and regularly train the entire team. One hour of prevention saves hundreds of hours of incident response.
Conclusion
The OWASP Top 10 is not an exhaustive list of all possible threats, but it covers the vast majority of real-world attacks. If your application eliminates these risks, it is more secure than 90% of the web.
The key is not to treat security as a checklist you tick off before launch. Security must be part of every phase of development -- from design through implementation, testing, and operations. And because the security landscape is constantly evolving, it is wise to have someone on your team or within reach who understands this domain and keeps up with current developments.


