Why security and compliance matter for Backend Engineers
Backends hold the keys: user data, business logic, and integrations. Strong security prevents data breaches, downtime, and legal risk. Compliance (like handling PII correctly) keeps your product shippable in real markets. Mastering this skill lets you design safer APIs, pass audits, and build trust.
What you’ll be able to do
- Build APIs with safe defaults, input validation, and least privilege.
- Protect data in transit and at rest with modern cryptography (no custom crypto).
- Manage secrets safely, rotate them, and reduce blast radius.
- Implement RBAC, audit trails, and security headers.
- Handle PII responsibly: minimize, mask, retain only as needed.
Who this is for
- Backend engineers shipping APIs or services.
- Full‑stack developers who touch backend logic or data.
- DevOps/SREs supporting secure runtime and config.
Prerequisites
- Comfort with at least one backend stack (Node, Python, Go, Java, etc.).
- Basics of HTTP, REST, and databases (SQL or NoSQL).
- Ability to read environment variables and configure services.
Learning path (practical roadmap)
Milestone 1 — Get your threat basics straight (OWASP + trust boundaries)
- Skim the OWASP Top 10 categories. Learn what each roughly means and how it shows up in APIs.
- Map trust boundaries in your service: client, API, DB, third‑party APIs, queues.
- Identify data classifications in your app: PII, secrets, public.
Milestone 2 — Make input boring and predictable
- Add strict validation and canonicalization on every external input (path, query, headers, body).
- Use parameterized queries or safe ORMs for all database access.
- Return clear 4xx errors when validation fails.
Milestone 3 — Tame secrets and keys
- Load secrets from environment or a secrets manager, never from code or git history.
- Scope secrets with least privilege (per‑service, per‑env). Rotate regularly.
- Add deny‑by‑default config if a secret is missing.
Milestone 4 — Encrypt in transit and at rest
- Serve only HTTPS, enforce HSTS, and disable weak ciphers.
- Use modern hashing (bcrypt/argon2) for passwords.
- If you must encrypt fields, use authenticated encryption (e.g., AES‑GCM) with managed keys.
Milestone 5 — Authorize like a pro (RBAC + scopes)
- Define clear roles and permissions. Apply least privilege.
- Centralize authorization checks; fail closed.
- Write unit tests for critical permission paths.
Milestone 6 — Log what matters, responsibly
- Emit structured logs for security‑relevant events (auth, permission checks, data exports, config changes).
- Mask secrets in logs; avoid storing raw PII where not needed.
- Set retention and access controls for logs.
Milestone 7 — Ship secure defaults
- Enable security headers, rate limits, timeouts, and request size limits.
- Harden dependencies; pin versions; enable automatic updates where safe.
- Run regular dependency and container scans; patch quickly.
Milestone 8 — Handle PII and privacy
- Minimize collection; prefer pseudonymized identifiers.
- Set and document retention; delete on schedule.
- Provide ways to export or delete user data when required by policy.
Worked examples
1) Prevent SQL injection with parameterized queries (Node + pg)
// Bad: string interpolation (vulnerable)
// const rows = await db.query(`SELECT * FROM users WHERE email = '${email}'`);
// Good: parameterized query
const { Client } = require('pg');
const client = new Client();
await client.connect();
async function findUserByEmail(email) {
const res = await client.query(
'SELECT id, email FROM users WHERE email = $1',
[email]
);
return res.rows[0] || null;
}
Why it works: the driver sends data separately from the query plan, so attacker input cannot alter SQL structure.
2) Secure password storage with bcrypt (Python + passlib)
from passlib.hash import bcrypt
# Hashing during signup
hashed = bcrypt.hash(plain_password) # auto-salt, configurable rounds
# Verifying during login
if bcrypt.verify(input_password, hashed):
authenticate_user()
else:
deny()
Notes: never store plaintext or reversible encryption. Tune cost factor to your performance budget.
3) Enforce HTTPS and HSTS (Express)
const express = require('express');
const helmet = require('helmet');
const app = express();
app.enable('trust proxy'); // if behind a proxy/load balancer
app.use(helmet());
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true, preload: false }));
// Redirect HTTP to HTTPS if needed
app.use((req, res, next) => {
if (req.secure) return next();
return res.redirect(301, `https://${req.headers.host}${req.url}`);
});
HSTS tells browsers to always use HTTPS for your domain, preventing downgrade attacks.
4) Field-level encryption with AES-GCM (Node)
const crypto = require('crypto');
function encryptField(plaintext, key32) {
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-256-gcm', key32, iv);
const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
return { iv: iv.toString('base64'), ct: ciphertext.toString('base64'), tag: tag.toString('base64') };
}
function decryptField(bundle, key32) {
const iv = Buffer.from(bundle.iv, 'base64');
const ct = Buffer.from(bundle.ct, 'base64');
const tag = Buffer.from(bundle.tag, 'base64');
const decipher = crypto.createDecipheriv('aes-256-gcm', key32, iv);
decipher.setAuthTag(tag);
const plaintext = Buffer.concat([decipher.update(ct), decipher.final()]).toString('utf8');
return plaintext;
}
// Key management: load from a secure source, rotate on schedule.
const key32 = crypto.randomBytes(32); // example only
Use authenticated encryption (GCM) to detect tampering. Never hardcode keys; load securely and rotate.
5) RBAC middleware (Node)
const permissions = {
admin: ['user:read', 'user:write', 'export:pii'],
manager: ['user:read'],
user: []
};
function allow(requiredPerm) {
return (req, res, next) => {
const role = req.user?.role || 'user';
const allowed = permissions[role]?.includes(requiredPerm);
if (!allowed) return res.status(403).json({ error: 'forbidden' });
next();
};
}
// Example usage: app.get('/admin/export', auth, allow('export:pii'), handler)
Keep authorization centralized and testable. Default to deny.
6) Security audit logs (JSON, minimal PII)
function auditLog({ actorId, action, resource, result, ip, meta }) {
const entry = {
ts: new Date().toISOString(),
actorId,
action,
resource,
result, // success | deny | error
ip,
meta
};
console.log(JSON.stringify({ type: 'audit', ...entry }));
}
// Example: auditLog({ actorId: 'u_123', action: 'login', resource: 'self', result: 'success', ip: '203.0.113.7' })
Never log secrets. Limit PII; use pseudonymous IDs where possible. Control access to logs.
Drills and exercises
- Identify trust boundaries in one of your services; list external inputs and outputs.
- Add strict validation to three endpoints (path, query, body). Reject invalid types and ranges.
- Replace any string-built SQL with parameterized queries. Add tests against injection payloads.
- Rotate one secret safely. Prove old tokens stop working.
- Enable HSTS and verify via response headers.
- Add RBAC checks to two sensitive operations. Write unit tests for allow/deny.
- Create structured audit logs for login, password change, and data export.
- Remove unnecessary PII fields from one table or log. Document retention for the rest.
Common mistakes and debugging tips
Using validation only on the frontend
Frontends are untrusted. Always validate on the server. Tip: add server tests for invalid payloads.
Hashing passwords with fast hashes
SHA‑256/MD5 are fast and unsafe for passwords. Use bcrypt/Argon2 with a reasonable cost. Verify performance under load.
Hardcoding secrets
Secrets in code or git last forever. Load from environment or a secrets manager. Deny start if missing.
Custom crypto
Do not invent algorithms. Use vetted libraries, modern modes (AEAD), and standard key sizes.
Allow‑by‑default authorization
Fail closed. If a permission check is unsure, deny. Cover critical paths with tests.
Verbose PII in logs
Mask or avoid PII in logs. If you must include an identifier, prefer pseudonymous IDs; set retention.
Mini project: Harden a tiny Note API
Goal: Take a simple CRUD Note API and apply security and compliance upgrades end‑to‑end.
Requirements
- Validation on all inputs (title length, allowed chars, size limits).
- Auth: session or token with RBAC (user vs admin). Admin can list all notes; users only their own.
- Parameterized DB queries.
- HTTPS only with HSTS (or reverse proxy enforcing it in local env).
- Audit logs for create, read (own), read (admin), update, delete.
- Secrets from environment; app refuses to start if missing.
- Optional: encrypt note content at rest with AES‑GCM.
- Privacy: do not log note content; mask tokens in logs. Add a retention config for logs.
Acceptance checklist
- Invalid payloads return 400 with clear error messages.
- Unauthorized requests return 401; forbidden actions return 403.
- SQL injection attempts fail and are logged without leaking stack traces.
- Security headers include HSTS and sensible defaults.
- Secrets never appear in logs; missing secrets block startup.
- Audit log shows who did what, when, and the result.
Subskills
OWASP Basics
Outcome: You can recognize common web risks (Top 10), sketch threat models, and name typical mitigations.
Estimated time: 45–60 min
Input Sanitization
Outcome: You can validate and canonicalize inputs, avoid injection, and return safe errors.
Estimated time: 45–90 min
Secrets Management Basics
Outcome: You can load secrets securely, avoid hardcoding, rotate credentials, and scope permissions.
Estimated time: 45–75 min
Encryption In Transit And At Rest
Outcome: You can enforce HTTPS/HSTS and use authenticated encryption (e.g., AES‑GCM) for sensitive fields.
Estimated time: 60–120 min
RBAC And Permissions
Outcome: You can design roles/scopes, centralize checks, and test deny/allow paths.
Estimated time: 60–120 min
Auditing And Access Logs
Outcome: You can produce structured, privacy‑aware audit logs with retention controls.
Estimated time: 45–90 min
Secure Defaults And Hardening
Outcome: You can enable security headers, rate limits, timeouts, and dependency scanning.
Estimated time: 60–120 min
Privacy And PII Handling Basics
Outcome: You can minimize collection, mask data, set retention, and support deletion/export workflows.
Estimated time: 60–120 min
Next steps
- Work through the subskills in order. They build on each other.
- Complete the mini project to integrate everything.
- Take the skill exam below. Anyone can take it for free; if you are logged in, your progress will be saved.