Web Security Best Practices - OWASP Top 10 Countermeasures

20 min read | 2025.12.02

Web application security is a critical element that developers should consider from the start. The Top 10 published by OWASP (Open Web Application Security Project) is a list of the most common and dangerous vulnerabilities that all developers should understand. This article explains each OWASP Top 10 vulnerability and practical countermeasures.

OWASP Top 10 2021

Vulnerability List

RankCategoryDescription
A01Broken Access ControlMissing or inadequate authorization checks
A02Cryptographic FailuresInadequate protection of sensitive data
A03InjectionSQL, XSS, command injection
A04Insecure DesignDesign without security consideration
A05Security MisconfigurationDefault settings, unnecessary features
A06Vulnerable ComponentsLibraries with known vulnerabilities
A07Identification and Authentication FailuresAuthentication mechanism deficiencies
A08Software and Data Integrity FailuresInsecure updates, CI/CD
A09Security Logging and Monitoring FailuresFailure to detect attacks
A10SSRFServer-Side Request Forgery

A03: Injection Attacks

SQL Injection

// Vulnerable code
async function getUser(userId: string) {
  const query = `SELECT * FROM users WHERE id = '${userId}'`;
  return db.query(query);
}
// userId = "1' OR '1'='1" → All users are retrieved

// Safe code (parameterized query)
async function getUser(userId: string) {
  const query = 'SELECT * FROM users WHERE id = $1';
  return db.query(query, [userId]);
}

// Using ORM (Prisma)
async function getUser(userId: string) {
  return prisma.user.findUnique({
    where: { id: userId }
  });
}

XSS (Cross-Site Scripting)

// Vulnerable code
function renderComment(comment: string) {
  document.getElementById('comments').innerHTML = comment;
}
// comment = "<script>alert('XSS')</script>" → Script executes

// Safe code (escape)
function escapeHtml(text: string): string {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

function renderComment(comment: string) {
  const escaped = escapeHtml(comment);
  document.getElementById('comments').innerHTML = escaped;
}

// Safer: use textContent
function renderComment(comment: string) {
  document.getElementById('comments').textContent = comment;
}

// React/Vue automatically escape
function CommentList({ comments }: { comments: string[] }) {
  return (
    <ul>
      {comments.map((c, i) => <li key={i}>{c}</li>)}
    </ul>
  );
}

Content Security Policy (CSP)

// CSP configuration in Express.js
import helmet from 'helmet';

app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", "'strict-dynamic'"],
    styleSrc: ["'self'", "'unsafe-inline'"],
    imgSrc: ["'self'", "data:", "https:"],
    connectSrc: ["'self'", "https://api.example.com"],
    fontSrc: ["'self'", "https://fonts.gstatic.com"],
    objectSrc: ["'none'"],
    baseUri: ["'self'"],
    frameAncestors: ["'none'"],
    upgradeInsecureRequests: [],
  },
}));

A01: Broken Access Control

Authorization Check Implementation

// Vulnerable code
app.get('/api/users/:id', async (req, res) => {
  const user = await db.user.findUnique({ where: { id: req.params.id } });
  res.json(user);  // Anyone can access
});

// Safe code
app.get('/api/users/:id', authenticate, async (req, res) => {
  const userId = req.params.id;
  const currentUser = req.user;

  // Only self or admin can access
  if (userId !== currentUser.id && currentUser.role !== 'admin') {
    return res.status(403).json({ error: 'Forbidden' });
  }

  const user = await db.user.findUnique({ where: { id: userId } });
  res.json(user);
});

IDOR (Insecure Direct Object Reference) Prevention

// Vulnerable code
app.get('/api/orders/:orderId', async (req, res) => {
  const order = await db.order.findUnique({
    where: { id: req.params.orderId }
  });
  res.json(order);  // Can see others' orders
});

// Safe code
app.get('/api/orders/:orderId', authenticate, async (req, res) => {
  const order = await db.order.findFirst({
    where: {
      id: req.params.orderId,
      userId: req.user.id  // Owner check
    }
  });

  if (!order) {
    return res.status(404).json({ error: 'Order not found' });
  }

  res.json(order);
});

RBAC (Role-Based Access Control)

// Role definition
const ROLES = {
  admin: ['read', 'write', 'delete', 'manage_users'],
  editor: ['read', 'write'],
  viewer: ['read'],
} as const;

// Authorization middleware
function authorize(...requiredPermissions: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    const userRole = req.user?.role;
    const userPermissions = ROLES[userRole] || [];

    const hasPermission = requiredPermissions.every(
      p => userPermissions.includes(p)
    );

    if (!hasPermission) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }

    next();
  };
}

// Usage
app.delete('/api/posts/:id',
  authenticate,
  authorize('delete'),
  deletePost
);

A07: Identification and Authentication Failures

Secure Password Handling

import bcrypt from 'bcrypt';
import crypto from 'crypto';

// Password hashing
async function hashPassword(password: string): Promise<string> {
  const saltRounds = 12;
  return bcrypt.hash(password, saltRounds);
}

// Password verification
async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

// Password strength validation
function validatePasswordStrength(password: string): { valid: boolean; errors: string[] } {
  const errors: string[] = [];

  if (password.length < 12) {
    errors.push('Password must be at least 12 characters');
  }
  if (!/[A-Z]/.test(password)) {
    errors.push('Must include uppercase letters');
  }
  if (!/[a-z]/.test(password)) {
    errors.push('Must include lowercase letters');
  }
  if (!/[0-9]/.test(password)) {
    errors.push('Must include numbers');
  }
  if (!/[!@#$%^&*]/.test(password)) {
    errors.push('Must include special characters');
  }

  return { valid: errors.length === 0, errors };
}

Brute Force Prevention

import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';

// Rate limiting (login endpoint)
const loginLimiter = rateLimit({
  store: new RedisStore({
    client: redisClient,
    prefix: 'rl:login:'
  }),
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 5,  // Max 5 attempts
  skipSuccessfulRequests: true,  // Don't count successful requests
  message: {
    error: 'Too many login attempts. Please try again in 15 minutes.'
  }
});

app.post('/api/auth/login', loginLimiter, loginHandler);

// Account lock feature
async function checkAccountLock(userId: string): Promise<boolean> {
  const lockKey = `lock:${userId}`;
  const failKey = `fail:${userId}`;

  const lockUntil = await redis.get(lockKey);
  if (lockUntil && Date.now() < parseInt(lockUntil)) {
    return true;  // Locked
  }

  return false;
}

async function recordFailedAttempt(userId: string): Promise<void> {
  const failKey = `fail:${userId}`;
  const lockKey = `lock:${userId}`;

  const attempts = await redis.incr(failKey);
  await redis.expire(failKey, 3600);  // Reset after 1 hour

  if (attempts >= 5) {
    // Lock for 30 minutes
    await redis.set(lockKey, Date.now() + 30 * 60 * 1000);
    await redis.expire(lockKey, 1800);
  }
}

CSRF (Cross-Site Request Forgery) Prevention

CSRF Token

import csrf from 'csurf';

// CSRF middleware
const csrfProtection = csrf({
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'strict'
  }
});

// Endpoint to get token
app.get('/api/csrf-token', csrfProtection, (req, res) => {
  res.json({ csrfToken: req.csrfToken() });
});

// Protected endpoint
app.post('/api/transfer', csrfProtection, (req, res) => {
  // CSRF token is automatically verified
  // ...
});
// Frontend usage
async function makeRequest(url: string, data: object) {
  // Get CSRF token
  const { csrfToken } = await fetch('/api/csrf-token').then(r => r.json());

  return fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': csrfToken,
    },
    credentials: 'include',
    body: JSON.stringify(data),
  });
}

Security Headers

import helmet from 'helmet';

app.use(helmet());

// Individual settings
app.use(helmet.frameguard({ action: 'deny' }));  // Clickjacking prevention
app.use(helmet.noSniff());  // MIME type sniffing prevention
app.use(helmet.xssFilter());  // XSS filter
app.use(helmet.referrerPolicy({ policy: 'strict-origin-when-cross-origin' }));

// Custom headers
app.use((req, res, next) => {
  res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
  next();
});

Response Header Checklist

Security Header Checklist:

□ Content-Security-Policy
□ Strict-Transport-Security
□ X-Frame-Options
□ X-Content-Type-Options
□ Referrer-Policy
□ Permissions-Policy
□ X-XSS-Protection (legacy)

Summary

Web security requires continuous effort.

Development Checklist

  1. Input Validation: Validate and sanitize all user input
  2. Authentication/Authorization: Implement proper access controls
  3. Encryption: Encrypt sensitive data, force HTTPS
  4. Session Management: Secure cookie settings
  5. Dependencies: Regular vulnerability scanning

Operations Checklist

  1. Logging/Monitoring: Anomaly detection mechanisms
  2. Incident Response: Response plan preparation
  3. Regular Audits: Penetration testing
  4. Education: Security awareness training for developers

Security is not a “feature to add later” but an element to build in from the design stage.

← Back to list