DEV Community

Alex Chen
Alex Chen

Posted on

Web Security Basics: Every Developer Must Know (2026)

Web Security Basics: Every Developer Must Know (2026)

Security isn't just for security teams. Every developer who writes code that touches the web needs to know these basics. Your users are counting on it.

HTTPS & TLS: The Foundation

How HTTPS works (simplified):
1. Browser connects to server
2. Server presents its SSL/TLS certificate (proves identity)
3. Browser verifies certificate with a trusted Certificate Authority (CA)
4. Both parties negotiate encryption parameters (TLS handshake)
5. All data is encrypted before transmission

Without HTTPS:
→ Anyone on the network can read/modify your traffic (coffee shop WiFi!)
→ Login credentials sent in plain text
→ Mixed content warnings in browsers

With HTTPS:
→ Data is encrypted end-to-end
→ Users see the padlock icon
→ Required for modern features (Service Workers, HTTP/2, etc.)
Enter fullscreen mode Exit fullscreen mode
# Get free SSL certificate with Let's Encrypt:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Auto-renews! Set up cron if needed.

# Test your SSL configuration:
curl -Iv https://yourdomain.com          # Check headers
openssl s_client -connect yourdomain.com:443 # Check cert details
# Online test: ssllabs.com/ssltest (gives grade A-F)
Enter fullscreen mode Exit fullscreen mode

Authentication Essentials

// === Password Storage (NEVER store plain text!) ===
// ❌ Terrible:
db.users.insert({ email, password: 'secret123' }); // Anyone who sees DB has all passwords!

// ✅ Use bcrypt (purpose-built for passwords):
const bcrypt = require('bcrypt');
const SALT_ROUNDS = 12; // Higher = slower = more secure (but don't overdo it)

async function hashPassword(plainPassword) {
  return await bcrypt.hash(plainPassword, SALT_ROUNDS);
}

// Verify at login:
async function login(email, password) {
  const user = await db.users.findOne({ email });
  if (!user) throw new AuthError('Invalid credentials');

  const valid = await bcrypt.compare(password, user.passwordHash);
  if (!valid) throw new AuthError('Invalid credentials');

  return createSession(user);
}
// bcrypt automatically handles salt → every hash is unique even for same password!
// bcrypt is slow by design → makes brute-force attacks expensive

// === Session Management ===
// After successful login, how do you keep user "logged in"?

// Option A: Session cookies (server-side):
app.use(session({
  secret: crypto.randomBytes(32).toString('hex'), // Random! Long! Change per deploy!
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,       // Only send over HTTPS
    httpOnly: true,     // JavaScript can't read (prevents XSS token theft!)
    sameSite: 'strict', // Prevents CSRF attacks
    maxAge: 3600000,    // 1 hour
  }
}));
// Server stores session data → client only gets an opaque session ID cookie

// Option B: JWT tokens (stateless):
const jwt = require('jsonwebtoken');

function generateToken(user) {
  return jwt.sign(
    { userId: user.id, role: user.role },
    process.env.JWT_SECRET,        // Keep this SECRET!
    { expiresIn: '15m' }           // Short-lived access tokens!
  );
}
// Client stores token (usually localStorage or httpOnly cookie)
// Server verifies signature on each request (no DB lookup needed)
// ⚠️ JWT can't be easily revoked — use short expiry + refresh tokens

// === Multi-Factor Authentication (MFA) ===
// Something you know (password) + something you have (phone/app)
// Libraries: speakeasy, otplib, qrcode

const speakeasy = require('speakeasy');
const QRCode = require('qrcode');

// Setup MFA for user:
const secret = speakeasy.generateSecret({ name: `MyApp (${user.email})` });
// Save secret.encrypted to DB (encrypt it!)

// Show user QR code to scan with Authenticator app:
const qrUrl = secret.otpauth_url;
QRCode.toDataURL(qrUrl); // Send to frontend to display

// Verify MFA code during login:
const verified = speakeasy.totp.verify({
  secret: user.mfaSecret,
  encoding: 'base32',
  token: userInputCode,      // 6-digit code from authenticator app
  window: 2                  // Allow 2 periods of clock drift (~60 seconds)
});
Enter fullscreen mode Exit fullscreen mode

Common Vulnerabilities & How to Prevent Them

XSS (Cross-Site Scripting)

// Attack: Attacker injects JavaScript into your page
// <script>stealCookies()</script>
// <img src=x onerror="alert(1)">

// Prevention:
// 1. Output encoding (escape user content before rendering):
import escapeHtml from 'escape-html';
const safeOutput = escapeHtml(userInput); // Converts < to &lt; etc.

// 2. Content Security Policy (CSP): tells browser which scripts are allowed:
// In Express/helmet:
helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'", 'cdn.example.com'],
    styleSrc: ["'self'", "'unsafe-inline'"], // Needed for some CSS frameworks
    imgSrc: ["'self'", 'data:', 'https:'],
    connectSrc: ["'self'", 'api.example.com'],
    fontSrc: ["'self'"],
    objectSrc: ["'none'"],
    frameSrc: ["'none'"],
  },
});

// 3. HttpOnly cookies (JavaScript can't read them):
cookie: { httpOnly: true }

// 4. Sanitize HTML (if you MUST allow some HTML):
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirtyHTML); // Removes dangerous tags
Enter fullscreen mode Exit fullscreen mode

CSRF (Cross-Site Request Forgery)

// Attack: User visits evil.com while logged into your site.
// evil.com has: <img src="https://yoursite.com/transfer?to=attacker&amount=1000">
// Browser automatically sends the request WITH the user's cookies!

// Prevention: CSRF Tokens
const csrf = require('csurf');

// Setup middleware:
app.use(csrf({ cookie: true }));

// Include token in forms (server renders it):
app.get('/form', (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});
// In template: <input type="hidden" name="_csrf" value="<%= csrfToken %>">

// For API requests: include in header or custom header:
// X-CSRF-Token header (checked automatically by csurf middleware)

// Alternative: SameSite=strict cookies (modern approach):
// If cookie has sameSite: strict, browser won't send it on cross-site requests.
// This effectively prevents CSRF without tokens!
Enter fullscreen mode Exit fullscreen mode

SQL Injection

// Attack: User input modifies SQL query logic
// Input: ' OR 1=1 --
// Query becomes: SELECT * FROM users WHERE email='' OR 1=1--' AND password='...'
// Returns ALL users → bypasses authentication!

// Prevention: Parameterized queries (ALWAYS!)
// ❌ Vulnerable:
const result = await db.query(`SELECT * FROM users WHERE email = '${email}'`);

// ✅ Safe (parameterized):
const result = await db.query('SELECT * FROM users WHERE email = $1', [email]);
// Database treats $1 as DATA, never as SQL code.

// Using ORM (most do this automatically):
const user = await User.findOne({ where: { email } }); // Safe by default
// But be careful with raw queries in ORMs:
await sequelize.query(`SELECT * FROM ${tableName}`); // tableName could be malicious!
Enter fullscreen mode Exit fullscreen mode

Security Headers Checklist

// Express + helmet.js gives you most of these out of the box:
const helmet = require('helmet');
app.use(helmet());

// Manual verification checklist:

// 1. X-Content-Type-Options: nosniff
//    → Prevents browser from MIME-type sniffing (treats file as declared type)

// 2. X-Frame-Options: DENY or SAMEORIGIN
//    → Prevents clickjacking attacks (your site embedded in attacker's iframe)

// 3. Strict-Transport-Security: max-age=31536000; includeSubDomains
//    → Forces HTTPS for 1 year after first visit

// 4. X-XSS-Protection: 0 (DISABLED!)
//    → Old browser feature, can introduce vulnerabilities. Use CSP instead.

// 5. Content-Security-Policy (see above under XSS)

// 6. Referrer-Policy: strict-origin-when-cross-origin
//    → Controls what info is sent in Referer header

// 7. Permissions-Policy: camera=(), microphone=(), geolocation=()
//    → Controls which browser APIs your site can use

// Check your headers:
curl -I https://yoursite.com | grep -iE 'x-content|x-frame|hsts|csp|referrer'
// Or online: securityheaders.com (gives grade A+ to F)
Enter fullscreen mode Exit fullscreen mode

What security topic confuses you most? What's the worst vulnerability you've found in production?

Follow @armorbreak for more practical developer guides.

Top comments (0)