🎁 Free starter workshopHaving a SW issue?
Back to blog
Web DevelopmentSecurityOWASPWebBackend

Web Application Security: OWASP Top 10 in Practice

Lukáš HusoMarch 31, 20269 min read
Web Application Security: OWASP Top 10 in Practice
Photo: Markus Spiske / Unsplash

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.

$4.88M
average cost of a data breach (2024)
43%
of attacks target web applications
200+ days
average time to detect a breach

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:

  1. Threat modeling during design -- identify risks before development begins
  2. Secure coding standards -- the entire team must know and follow basic rules
  3. Automated testing -- SAST (static analysis), DAST (dynamic testing), dependency scanning
  4. Security-focused code review -- every PR should be reviewed for security aspects
  5. Penetration testing -- regular testing by an external team
  6. 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.

Get your custom price

Our configurator shows you an indicative price for your project in 2 minutes.

Related Articles

Web App Performance Optimization
Web DevelopmentPerformanceWeb

Web App Performance Optimization

A practical guide to web application performance. Core Web Vitals, image optimization, code splitting, caching strategies, and measuring real-world performance.

May 5, 202610 min read
SEO Disasters: Websites Google Can't Find

SEO Disasters: Websites Google Can't Find

Real stories of SEO catastrophes — from invisible SPAs to buying thousands of backlinks. How not to destroy your site in Google's eyes.

April 9, 20267 min read
Hosting Horror Stories: Why It Matters Where Your App Runs
Web DevelopmentHostingServer

Hosting Horror Stories: Why It Matters Where Your App Runs

An app on a laptop under a desk, unlimited hosting that crashes at 100 users, a provider that went bankrupt overnight. Real stories.

May 7, 20267 min read