All articles
Backend from First Principles

Backend Security: A Mental Model for Backend Engineers

Most security bugs start with one dangerous sentence: "The user will probably not do that." Attackers build careers out of doing exactly that.

2026-04-099 min read

This is part 16 of my backend-from-first-principles series. Security is too big to cover exhaustively in one article, so this is not a checklist of every possible attack. It is the core mental model: where do backend vulnerabilities usually come from, and what habits prevent the largest classes of them?


The Mindset: Find the Assumption

Happy-path users click the intended button, submit valid forms, and follow the UI. Attackers don't. They edit request bodies, guess IDs, replay old tokens, inject operators, decode JWTs, upload weird filenames, and call endpoints your frontend never exposes.

Most vulnerabilities hide in assumptions:

  • "This value came from our form, so it must be valid."
  • "Nobody will know this admin URL."
  • "The user only sees their own invoice ID."
  • "This markdown will just render as text."
  • "This JWT is signed, so it must be private."

The security question is always: what happens if a hostile user controls this value?


The Mental Model: The Biohazard Lab Airlock

In a high-security biohazard lab, nothing crosses a boundary casually. A sample arrives from the outside world. Before it enters the clean room, it goes through an airlock: labeled, scanned, sealed, sanitized, and tracked.

Backend security works the same way. Most vulnerabilities happen when data crosses a boundary and gets interpreted in the wrong context:

Browser input   →  API validation
User string     →  SQL query
Markdown        →  HTML
Cookie          →  Authenticated session
User ID         →  Database row access
Filename        →  Shell command

The data itself is not always dangerous. The danger appears when the system treats untrusted data as trusted code, trusted identity, or trusted permission.

The rule: every boundary needs an airlock. Validate, parameterize, authorize, sanitize, limit, and log before letting data move deeper into the system.

Injection: When Data Becomes Code

Injection attacks happen when your application confuses user-provided data with executable instructions.

SQL Injection

The classic mistake is building SQL by concatenating raw strings:

const query =
  \"SELECT * FROM users WHERE email = '\" + email + \"'\";

If the attacker enters this as the email:

' OR 1=1 --

The query becomes something completely different. The quote closes your string, OR 1=1 makes the condition always true, and -- comments out the rest. In login flows, that can become account takeover. In destructive paths, it can become DROP TABLE users.

The fix is parameterized queries:

db.query(
  \"SELECT * FROM users WHERE email = $1\",
  [email]
);

The database now receives two separate things: the query template and the data. Whatever the user typed stays data. It never becomes SQL.

NoSQL and Command Injection

NoSQL databases are not magically safe. If you pass raw user JSON directly into a Mongo query, attackers can inject operators like $ne ("not equal") and change the meaning of the query.

Command injection is the same bug in a different interpreter. If you build shell commands by concatenating a user filename:

ffmpeg \" + filename

an attacker can smuggle shell syntax into the filename. The fix is to avoid the shell when possible and use APIs that pass the command and arguments separately.

The pattern: SQL, NoSQL, shell, HTML, regex — every interpreter is dangerous when untrusted input is allowed to become instructions.

Authentication: Passwords Are Radioactive

Authentication answers: who is this user? If you're building a real product, use a trusted auth provider unless you have a strong reason not to. Password systems are easy to build badly and painful to fix after launch.

If you must store passwords, the rules are non-negotiable:

  • Never store plaintext passwords. If the database leaks, every user's password leaks. Attackers will try those credentials on Gmail, banks, GitHub, and everywhere else.
  • Hash passwords, don't encrypt them. Hashing is one-way. You verify by hashing the submitted password again and comparing outputs.
  • Use a unique salt per user. A salt is random data added before hashing. It makes two identical passwords produce different hashes and destroys the usefulness of rainbow tables.
  • Use slow password hashers. MD5 and SHA-256 are too fast. GPUs can brute-force billions of them per second. Use Argon2id or Bcrypt with a cost factor tuned for your system.

The point of slow hashing is not to make login slow for users. It is to make cracking millions of leaked passwords economically miserable for attackers.


Sessions vs JWTs

After authentication, the server needs to remember the user. Two common models:

Stateful Sessions

The server creates a long random session ID, stores session data in a database or Redis, and sends only the ID to the browser in a cookie.

The big advantage: revocation. If an account is compromised, delete the session from the store. The attacker is logged out immediately.

JWTs

A JWT packages claims into a signed token. The server can verify the signature without storing session state. This is convenient, but it has a sharp edge: JWTs are hard to revoke before they expire.

The common workaround is short-lived access tokens plus longer-lived refresh tokens. If an access token is stolen, the blast radius is minutes, not days.

And one warning that never gets old: JWT payloads are Base64 encoded, not encrypted. Anyone holding the token can decode and read the claims. Never put secrets, passwords, API keys, or sensitive PII inside a JWT payload.

Cookie Flags

Whether you store a session ID or token in a cookie, the browser needs guardrails:

  • HttpOnly — JavaScript cannot read the cookie, which limits damage from XSS.
  • Secure — the cookie is sent only over HTTPS.
  • SameSite=Lax or Strict — the cookie is not freely sent on cross-site requests, which helps mitigate CSRF.

Rate Limiting: Slow the Attack Down

Rate limiting is not just about protecting uptime. It protects authentication systems from brute force and credential stuffing.

Use layers:

  • Per IP limit — blocks one noisy source, but botnets and VPNs can spread across thousands of IPs.
  • Per account limit — slows repeated attempts against one account, but attackers can try one password across many accounts.
  • Global limit — caps the total login pressure your system will accept, even if the attack is distributed.

No single limiter is enough. Together, they turn "try a billion passwords quickly" into "this is too slow and too expensive to continue."


Authorization: Can This User Do This?

Authentication says who the user is. Authorization says what they are allowed to do. Most serious data leaks live here.

BOLA / IDOR: The Horizontal Attack

Broken Object Level Authorization happens when a user changes an ID in a request and accesses someone else's object:

GET /invoices/5
GET /invoices/6

If user A can fetch user B's invoice by guessing the ID, authentication did nothing useful.

The fix is ownership checks near the data:

SELECT * FROM invoices
WHERE id = $1
  AND user_id = $2;

Do not rely on "the frontend only shows their own IDs." The frontend is not a security boundary. Also prefer UUIDs over sequential IDs so resources are not easy to enumerate.

If a user requests an invoice they do not own, return 404 Not Found, not 403 Forbidden. A 403 confirms the invoice exists. That tiny leak can become enumeration data for a bigger attack.

BFLA: The Vertical Attack

Broken Function Level Authorization happens when a normal user calls an admin function directly:

POST /admin/refund-order

Hiding the admin button in the UI is not security. The backend must explicitly check the user's role or permission before executing sensitive actions.

The principles:

  • Centralize authorization logic so every route doesn't invent its own rules.
  • Default deny — block unless explicitly allowed.
  • Test boundary violations — user A reading user B's data, regular user calling admin endpoints, expired session calling protected routes.

XSS and CSRF: Browser-Side Damage From Backend Mistakes

XSS

Cross-Site Scripting happens when an attacker gets your site to run their JavaScript in another user's browser. The common route: user-generated content that gets rendered as HTML.


If your comment system, markdown editor, profile bio, or rich-text field stores this and later renders it unsafely, every viewer becomes the victim.

The fix: sanitize user-provided markup before saving or rendering it, escape output by default, and use a strong Content Security Policy so the browser refuses inline scripts and only loads scripts from trusted domains.

CSRF

Cross-Site Request Forgery tricks a logged-in user's browser into sending a request to your site from an attacker's site. The browser includes cookies automatically, so the request looks authenticated.

Modern SameSite=Lax or SameSite=Strict cookie settings mitigate most CSRF risk for modern apps. High-risk flows can also use CSRF tokens, especially when supporting older browser behavior or complex cross-site flows.


Misconfigurations: The Boring Bugs That Burn You

Security is not only clever exploits. Many incidents are boring config mistakes.

  • Secrets in Git — never commit API keys, database URLs, JWT secrets, or cloud credentials. If a secret is committed, assume it is leaked and rotate it immediately.
  • Debug mode in production — debug logs and stack traces leak paths, queries, secrets, and implementation details. Production should usually run at info, not debug.
  • Missing security headers — use framework middleware to set safe defaults. Headers like X-Frame-Options protect against clickjacking by preventing hostile sites from embedding your app in an iframe.
  • Verbose error messages — never return raw database errors, stack traces, or internal service names to users. Log details internally; return generic messages externally.

Defense in Depth

No single layer is enough. The point of defense in depth is that one mistake should not become a breach.

Input validation
  → Parameterized queries
  → Output escaping / sanitization
  → Authentication
  → Point-of-access authorization
  → Rate limiting
  → Security headers
  → Monitoring and audit logs

If validation misses something, parameterized queries should still block SQL injection. If XSS sneaks through, HttpOnly cookies reduce token theft. If an attacker guesses an object ID, authorization near the database should still deny access. If all of that fails, monitoring should make the abuse visible.

Security is not one perfect wall. It is a series of imperfect doors, each locked independently.

The Mental Model Summary

Carry this forward:

  • Find the assumption — vulnerabilities often start where the developer assumed the user would behave.
  • The airlock model — every boundary crossing needs inspection: validate, parameterize, authorize, sanitize, limit, log.
  • Injection — never let user data become executable code. Use parameterized queries and argument arrays.
  • Passwords — never plaintext, never fast hashes. Use per-user salts and Argon2id or Bcrypt.
  • Sessions — stateful sessions are easier to revoke. JWTs need short lifetimes and careful payload discipline.
  • Cookies — use HttpOnly, Secure, and SameSite.
  • Rate limiting — layer IP, account, and global limits.
  • Authorization — check ownership and role on the backend, close to the data. Default deny.
  • XSS / CSRF — sanitize markup, escape output, use CSP, and configure cookies correctly.
  • Defense in depth — assume one layer will fail and make sure the next layer still holds.

The attacker does not care about your intended flow. They care about the one boundary where data gets misread as code, identity, or permission. A secure backend treats every crossing as suspicious, every assumption as a possible bug, and every layer as something that should fail safely.


This is part 16 of my "Backend from First Principles" series. Everything in this article comes from what I learned watching the Backend from First Principles playlist by Sriniously — specifically the video on backend security. I'm not claiming original research. This is me taking what I studied, internalizing it, and presenting it as a mental model that made the concepts stick for me. If it helps you too, even better.

Follow along for the next one.

#BackendEngineering #Security #WebSecurity #SystemDesign #Authentication #Authorization #LearningInPublic