PreBreachPreBreach
How it WorksMethodologyPricingBlog
Start Audit
HomeBlogHow to Make Web App Secure: A Developer's Guide to Shipping Safe Code in 2025
How to Make Web App Secure: A Developer's Guide to Shipping Safe Code in 2025

How to Make Web App Secure: A Developer's Guide to Shipping Safe Code in 2025

3/5/2026
by PreBreach Team
web application securityOWASP Top 10secure codingAI-generated code securityindie hacker security

Table of Contents

Key Takeaways (TL;DR)Why Learning How to Make Web App Secure Matters More Than EverThe Threat Landscape: What Attackers Actually ExploitHow to Make Web App Secure: Broken Access ControlThe ProblemVulnerable Pattern (Node.js/Express)Secure PatternPreventing Injection AttacksSQL Injection: Still Devastating in 2025Vulnerable Pattern (Python/Flask)Secure PatternNoSQL InjectionCryptographic Failures: Passwords, Secrets, and TransportPassword HashingSecrets ManagementSecurity Misconfiguration: The Silent KillerHTTP Security HeadersCORS MisconfigurationAuthentication and Session Management Done RightCross-Site Scripting (XSS): Still in the Top 5Dependency Security: Your Code Is Only as Secure as Your PackagesA Practical Security Checklist for Indie DevelopersAutomating Security for AI-Generated CodeActionable Next Steps: Start Today

Key Takeaways (TL;DR)

  • The OWASP Top 10 remains the gold standard checklist for web application security — broken access control is now the #1 risk.
  • Most breaches exploit well-known, preventable vulnerabilities: injection, misconfiguration, and broken authentication account for the vast majority of incidents.
  • AI-generated code from tools like Cursor, Bolt.new, and Lovable often introduces subtle security flaws that pass functional testing but fail security review.
  • You don't need a security team to make meaningful progress — parameterized queries, CSP headers, proper auth, and automated scanning cover 80%+ of real-world attack surface.
  • Every code example below is production-relevant and shows both the vulnerable and secure pattern side by side.

Why Learning How to Make Web App Secure Matters More Than Ever

In 2023, the average cost of a data breach reached $4.45 million according to IBM's Cost of a Data Breach Report. But you don't have to be an enterprise to be a target. Automated attack bots scan the entire IPv4 address space in under 45 minutes, meaning every deployed web app — including your side project — gets probed within hours of going live.

If you're an indie hacker or a developer shipping fast with AI coding tools, understanding how to make web app secure is non-negotiable. The code these tools generate is often functional but insecure by default. A Stanford study (2022) found that developers using AI coding assistants produced significantly less secure code than those who didn't, and were more likely to believe their code was secure when it wasn't.

This guide walks through the vulnerabilities that actually matter, with real code, real sources, and a prioritized action plan.

The Threat Landscape: What Attackers Actually Exploit

The OWASP Top 10 (2021) reorganized its risk categories based on data from over 500,000 applications. Here's what's most relevant to indie developers:

OWASP RankCategoryPrevalenceIndie Dev Risk
A01Broken Access Control94% of apps testedCritical — especially with role-based features
A02Cryptographic FailuresHighHigh — plaintext secrets, weak hashing
A03Injection94% of apps tested for some formCritical — SQL, NoSQL, command injection
A05Security Misconfiguration90% of apps testedVery High — default configs, open CORS
A07Identification & Auth FailuresHighHigh — weak session management, no MFA

Let's go deep on each of these with code you can actually use.

How to Make Web App Secure: Broken Access Control

The Problem

Broken access control (CWE-639: Authorization Bypass Through User-Controlled Key) means users can act outside their intended permissions. This is the single most common vulnerability class in modern web apps. In 2023, the HackerOne Top 10 vulnerability report confirmed that Insecure Direct Object Reference (IDOR) remains one of the most frequently reported bugs in bug bounty programs.

Vulnerable Pattern (Node.js/Express)

// VULNERABLE: No authorization check — any authenticated user can access any invoice
app.get('/api/invoices/:id', authenticate, async (req, res) => {
  const invoice = await Invoice.findById(req.params.id);
  res.json(invoice);
});

Secure Pattern

// SECURE: Verify the resource belongs to the requesting user
app.get('/api/invoices/:id', authenticate, async (req, res) => {
  const invoice = await Invoice.findOne({
    _id: req.params.id,
    userId: req.user.id  // Scoped to authenticated user
  });
  if (!invoice) {
    return res.status(404).json({ error: 'Invoice not found' });
  }
  res.json(invoice);
});

The fix is simple: always scope database queries to the authenticated user's ID or role. Never trust client-supplied IDs alone. For role-based access, use middleware that checks permissions before the route handler executes, not inside it.

Preventing Injection Attacks

SQL Injection: Still Devastating in 2025

SQL injection (CWE-89) has been on OWASP's list since its inception. The MOVEit Transfer breach (CVE-2023-34362) was a SQL injection vulnerability that compromised data from over 2,600 organizations including the BBC, British Airways, and multiple US government agencies.

AI coding tools frequently generate string-concatenated queries because they're the most common pattern in training data.

Vulnerable Pattern (Python/Flask)

# VULNERABLE: String formatting creates SQL injection vector
@app.route('/users/search')
def search_users():
    name = request.args.get('name')
    query = f"SELECT * FROM users WHERE name = '{name}'"
    result = db.engine.execute(query)
    return jsonify([dict(row) for row in result])

An attacker sends: /users/search?name=' OR '1'='1' -- and retrieves every user record.

Secure Pattern

# SECURE: Parameterized query with SQLAlchemy
from sqlalchemy import text

@app.route('/users/search')
def search_users():
    name = request.args.get('name')
    if not name or len(name) > 100:
        return jsonify({'error': 'Invalid search parameter'}), 400
    result = db.session.execute(
        text("SELECT id, name, email FROM users WHERE name = :name"),
        {'name': name}
    )
    return jsonify([dict(row._mapping) for row in result])

Key improvements: parameterized query (the database driver handles escaping), input length validation, and explicit column selection instead of SELECT * (which can leak sensitive fields).

NoSQL Injection

If you're using MongoDB, you're not immune. CWE-943 covers improper neutralization of special elements in data query logic.

// VULNERABLE: Accepts operator injection via JSON body
app.post('/api/login', async (req, res) => {
  const user = await User.findOne({
    email: req.body.email,
    password: req.body.password  // Attacker sends { "$ne": "" }
  });
});

// SECURE: Validate and cast input types explicitly
app.post('/api/login', async (req, res) => {
  const email = String(req.body.email);
  const password = String(req.body.password);
  const user = await User.findOne({ email });
  if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }
  // Issue session/token
});

Cryptographic Failures: Passwords, Secrets, and Transport

The OWASP A02 Cryptographic Failures category covers everything from storing passwords in plaintext to transmitting data over HTTP.

Password Hashing

Use bcrypt, scrypt, or Argon2id. Never use MD5, SHA-1, or SHA-256 alone for password storage. NIST's SP 800-63B Digital Identity Guidelines specifically recommends memory-hard hashing functions.

// VULNERABLE
const crypto = require('crypto');
const hash = crypto.createHash('sha256').update(password).digest('hex');
// No salt, fast hash — crackable in seconds with hashcat

// SECURE
const bcrypt = require('bcrypt');
const SALT_ROUNDS = 12;
const hash = await bcrypt.hash(password, SALT_ROUNDS);
// Adaptive cost factor, built-in salt, memory-hard

Secrets Management

Never commit API keys, database credentials, or JWT secrets to your repository. The GitGuardian 2024 State of Secrets Sprawl report found over 12.8 million new secret occurrences in public GitHub commits in a single year. Use environment variables at minimum, and a proper secrets manager (AWS Secrets Manager, Doppler, Infisical) for production.

Security Misconfiguration: The Silent Killer

Security misconfiguration (OWASP A05) is arguably the most common issue in apps built with AI tools because these tools optimize for "it works" not "it's locked down."

HTTP Security Headers

Add these headers to every response. The MDN Content Security Policy documentation provides a comprehensive reference.

// Express.js: Use helmet as a baseline, then customize
const helmet = require('helmet');

app.use(helmet());
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'"],       // No 'unsafe-inline' — prevents XSS
    styleSrc: ["'self'", "'unsafe-inline'"],  // Often needed for CSS-in-JS
    imgSrc: ["'self'", "data:", "https:"],
    connectSrc: ["'self'"],
    frameSrc: ["'none'"],
    objectSrc: ["'none'"],
    upgradeInsecureRequests: [],
  }
}));

// Also critical:
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true }));
app.disable('x-powered-by');

CORS Misconfiguration

Overly permissive CORS is rampant in AI-generated code:

// VULNERABLE: Allows any origin
app.use(cors({ origin: '*', credentials: true })); // This is contradictory and dangerous

// SECURE: Whitelist specific origins
const allowedOrigins = ['https://yourdomain.com', 'https://app.yourdomain.com'];
app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true
}));

Authentication and Session Management Done Right

The OWASP Session Management Cheat Sheet is the definitive reference here. Key rules:

  1. Set secure cookie attributes: HttpOnly, Secure, SameSite=Lax (or Strict).
  2. Use short-lived JWTs (15 minutes) with refresh tokens stored in HttpOnly cookies, not localStorage. PortSwigger's JWT attacks guide documents how localStorage-stored tokens enable XSS-based token theft.
  3. Implement rate limiting on authentication endpoints — credential stuffing attacks are automated and relentless.
  4. Regenerate session IDs after login to prevent session fixation.
// Secure cookie configuration for Express sessions
app.use(session({
  secret: process.env.SESSION_SECRET,
  name: '__Host-sid',  // __Host- prefix enforces Secure + path=/
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    maxAge: 15 * 60 * 1000,  // 15 minutes
    domain: undefined  // Don't set — __Host- prefix requires this
  },
  resave: false,
  saveUninitialized: false,
  store: new RedisStore({ client: redisClient })  // Never use in-memory store in production
}));

Cross-Site Scripting (XSS): Still in the Top 5

XSS (CWE-79) allows attackers to execute JavaScript in other users' browsers. Modern frameworks like React and Vue auto-escape output by default, but there are common escape hatches that AI tools love to use:

// VULNERABLE: React's escape hatch — dangerouslySetInnerHTML
function UserComment({ comment }) {
  return <div dangerouslySetInnerHTML={{ __html: comment.body }} />;
}

// SECURE: Use a sanitization library if you must render HTML
import DOMPurify from 'dompurify';

function UserComment({ comment }) {
  const clean = DOMPurify.sanitize(comment.body, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p'],
    ALLOWED_ATTR: ['href']
  });
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

Better yet, use a Markdown renderer that doesn't allow raw HTML, such as react-markdown with rehype-sanitize.

Dependency Security: Your Code Is Only as Secure as Your Packages

The Snyk State of Open Source Security report consistently finds that the average JavaScript project has dozens of known vulnerable dependencies. The Log4Shell vulnerability (CVE-2021-44228) demonstrated how a single dependency flaw can cascade across millions of applications.

Actionable steps:

  1. Run npm audit or yarn audit as part of your CI pipeline.
  2. Use npm audit signatures to verify package provenance.
  3. Pin dependency versions in production with lock files.
  4. Enable GitHub Dependabot or Snyk for automated vulnerability alerts.

A Practical Security Checklist for Indie Developers

Here's a prioritized checklist you can start working through today, ordered by impact:

  1. Parameterize all database queries — eliminates injection attacks.
  2. Add authorization checks to every API endpoint — scope queries to the authenticated user.
  3. Set HTTP security headers — use helmet for Express, or configure your framework's equivalent.
  4. Hash passwords with bcrypt/Argon2id — cost factor of 12+ for bcrypt.
  5. Configure CORS to specific origins — never use * with credentials.
  6. Use HttpOnly, Secure, SameSite cookies for sessions.
  7. Audit dependencies — run npm audit weekly at minimum.
  8. Enable HTTPS everywhere — use HSTS headers.
  9. Rate limit authentication endpoints — use express-rate-limit or equivalent.
  10. Validate and sanitize all user input — on the server, always. Client-side validation is a UX feature, not a security feature.

Automating Security for AI-Generated Code

When you're shipping fast with AI coding tools, manual security review for every generated file isn't realistic. This is where automated scanning becomes essential. Tools like OWASP ZAP provide free, open-source dynamic application security testing.

For a faster feedback loop specifically designed for indie hackers and developers using AI tools, PreBreach runs AI-powered security scans against your deployed app and highlights the exact vulnerabilities that tools like Cursor, Bolt.new, and Lovable commonly introduce — including IDOR, missing headers, and injection flaws.

Actionable Next Steps: Start Today

You don't need to do everything at once. Here's your 30-minute security sprint:

  1. Right now (5 min): Add helmet to your Express app or check your framework's security header configuration. Verify headers with SecurityHeaders.com.
  2. Today (10 min): Run npm audit or pip audit and fix critical vulnerabilities.
  3. Today (15 min): Search your codebase for raw SQL string concatenation, dangerouslySetInnerHTML, and cors({ origin: '*' }). Fix what you find.
  4. This week: Review every API endpoint for authorization checks. Ask yourself: "Can user A access user B's data by changing an ID in the URL?"
  5. This week: Run a scan with PreBreach or OWASP ZAP against your staging environment to catch what you missed.

Security isn't a one-time task — it's a development practice. But the good news is that the patterns above cover the vast majority of real-world attacks. Implement them consistently, automate what you can, and you'll be far ahead of most web applications on the internet.

Table of Contents

Key Takeaways (TL;DR)Why Learning How to Make Web App Secure Matters More Than EverThe Threat Landscape: What Attackers Actually ExploitHow to Make Web App Secure: Broken Access ControlThe ProblemVulnerable Pattern (Node.js/Express)Secure PatternPreventing Injection AttacksSQL Injection: Still Devastating in 2025Vulnerable Pattern (Python/Flask)Secure PatternNoSQL InjectionCryptographic Failures: Passwords, Secrets, and TransportPassword HashingSecrets ManagementSecurity Misconfiguration: The Silent KillerHTTP Security HeadersCORS MisconfigurationAuthentication and Session Management Done RightCross-Site Scripting (XSS): Still in the Top 5Dependency Security: Your Code Is Only as Secure as Your PackagesA Practical Security Checklist for Indie DevelopersAutomating Security for AI-Generated CodeActionable Next Steps: Start Today

Ready to get started?

Join our team of 5,000+ users who are already transforming their workflow with PreBreach.

5,000+ active users
Get PreBreach Pro

Plans starting from $29/month

PreBreach

Secure your vibe coding. Built for the new generation of AI-assisted developers.

All Systems Operational

Product

  • Pricing
  • Sample Report
  • Documentation

Resources

  • Blog
  • Contact

Connect

  • Twitter / X

© 2026 PreBreach Security. All rights reserved.

Privacy PolicyTerms of Service