securityxsscsrf

Web Security Basics for Developers

· Cosyslabs

Web security is not an optional add-on — it is a core responsibility of every developer who writes code that handles user data or runs in a browser. Vulnerabilities like XSS, SQL injection, and CSRF appear in new codebases every day, often because developers are unaware of how attacks work. This guide explains the most common attacks and their practical defenses with code examples.

Cross-Site Scripting (XSS)

XSS occurs when an attacker injects malicious scripts into content that other users view. The browser executes the script in the context of the vulnerable site, allowing the attacker to steal cookies, redirect users, or perform actions on their behalf.

<!-- Reflected XSS: URL parameter rendered without escaping -->
<!-- URL: /search?q=<script>document.cookie</script> -->
<p>Results for: <?php echo $_GET['q']; ?></p>
<!-- → browser executes the injected script -->

Defense — escape output:

// Never do this in React or vanilla JS:
element.innerHTML = userInput;                  // dangerous
document.write(userInput);                      // dangerous
eval(userInput);                                // dangerous

// Safe alternatives:
element.textContent = userInput;                // text only, no parsing
// React auto-escapes JSX:
<p>{userInput}</p>  // safe

// When you must render HTML: sanitize first
import DOMPurify from "dompurify";
element.innerHTML = DOMPurify.sanitize(userInput);

Defense — Content Security Policy (CSP):

Content-Security-Policy: 
  default-src 'self';
  script-src 'self' https://trusted-cdn.com;
  style-src 'self' 'nonce-RANDOM_NONCE_HERE';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';

A strict CSP blocks inline scripts and only allows scripts from approved origins, providing defense-in-depth against XSS.

SQL Injection

SQL injection occurs when user input is concatenated into SQL queries. The attacker can read, modify, or delete any data in the database.

// VULNERABLE — never do this:
const query = `SELECT * FROM users WHERE email = '${email}'`;
// Attacker sends: email = "' OR '1'='1"
// Query becomes: SELECT * FROM users WHERE email = '' OR '1'='1'
// → returns all users

// SAFE — parameterized queries:
// PostgreSQL (pg)
const result = await client.query(
  "SELECT * FROM users WHERE email = $1",
  [email]  // parameter, not concatenated
);

// MySQL (mysql2)
const [rows] = await conn.execute(
  "SELECT * FROM users WHERE email = ?",
  [email]
);

// Prisma ORM (auto-parameterized)
const user = await prisma.user.findFirst({ where: { email } });

Cross-Site Request Forgery (CSRF)

CSRF tricks authenticated users into performing unintended actions. An attacker's page can send authenticated requests to your site using the user's cookies.

<!-- Attacker's page — forces the victim's browser to make an authenticated request -->
<form action="https://bank.example.com/transfer" method="POST">
  <input name="to" value="attacker_account">
  <input name="amount" value="10000">
</form>
<script>document.forms[0].submit();</script>

Defense — CSRF tokens:

// Server: generate and embed a CSRF token in the form
const csrfToken = crypto.randomBytes(32).toString("hex");
req.session.csrfToken = csrfToken;
// Embed in form: <input type="hidden" name="_csrf" value="...token...">

// Server: validate on POST
if (req.body._csrf !== req.session.csrfToken) {
  return res.status(403).json({ error: "Invalid CSRF token" });
}

Defense — SameSite cookies:

Set-Cookie: session=abc123; SameSite=Strict; Secure; HttpOnly; Path=/

SameSite=Strict prevents cookies from being sent on cross-site requests, eliminating most CSRF scenarios.

Set-Cookie: session_id=s3cr3t; 
  Secure;         // only sent over HTTPS
  HttpOnly;       // not accessible via document.cookie (XSS protection)
  SameSite=Lax;   // sent on same-site and top-level navigation, not cross-site POST
  Path=/;
  Max-Age=3600;   // expires in 1 hour
  Domain=example.com
AttributeWhat it prevents
SecureNetwork interception (requires HTTPS)
HttpOnlyJavaScript access (mitigates XSS cookie theft)
SameSite=StrictCSRF attacks
Short Max-AgeSession hijacking from stolen cookies

Broken Authentication

Common authentication vulnerabilities:

// WEAK: MD5 or SHA-1 password hashing
const hash = crypto.createHash("md5").update(password).digest("hex"); // broken

// STRONG: bcrypt with work factor 12+
import bcrypt from "bcrypt";
const hash = await bcrypt.hash(password, 12);
const valid = await bcrypt.compare(enteredPassword, hash);

// Rate limiting login attempts (express-rate-limit)
import rateLimit from "express-rate-limit";
app.use("/auth/login", rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 10,                    // max 10 login attempts per window per IP
  message: { error: "Too many login attempts" },
}));

Sensitive Data Exposure

// Never log sensitive data:
console.log("User logged in:", user);  // may expose email, roles, etc.
logger.info("Auth", { userId: user.id }); // log only what you need

// Never include secrets in API responses:
const userResponse = {
  id: user.id,
  name: user.name,
  email: user.email,
  // passwordHash: user.passwordHash  ← NEVER include this
  // apiKey: user.apiKey              ← NEVER include this
};

// Use environment variables, not hardcoded secrets:
const apiKey = process.env.STRIPE_SECRET_KEY; // correct
const apiKey = "sk_live_abc123...";            // NEVER do this

Security Headers Checklist

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; ...

Tools

  • Hash Generator — verify passwords are hashed correctly
  • JWT Decoder — inspect authentication tokens
  • CORS Tester — check CORS configuration