Security Best Practices Guide
A thorough guide to application security: authentication, authorization, input validation, secrets management, and common vulnerability prevention.
Overview
Security is not a feature you add later—it is a foundation you build into every layer of your application. The following guide covers the essential practices for building secure software.
When to Apply
- Building any application that handles user data
- Processing payments or sensitive information
- Exposing APIs to the internet
- Working in regulated industries (healthcare, finance, etc.)
Authentication & Authorization
Use Proven Authentication Libraries
Never roll your own authentication. Use battle-tested libraries:
| Language | Recommended Library |
|---|---|
| Node.js | Passport.js, Auth.js |
| Python | Django Auth, Flask-Login |
| Java | Spring Security, OAuth2 |
| Go | casbin, gorilla/sessions |
Multi-Factor Authentication (MFA)
Require MFA for:
- Admin accounts
- Production access
- Financial operations
Authorization Patterns
Role-Based Access Control (RBAC)
User -> Role -> Permission -> Resource
Principle of Least Privilege
Grant only the permissions necessary for each role. Audit permissions quarterly.
Input Validation
Validate at the Boundary
# Python with Pydantic
from pydantic import BaseModel, EmailStr, constr
class CreateUserRequest(BaseModel):
email: EmailStr
password: constr(min_length=12)
role: constr(pattern=r'^(user|admin)$')
Sanitize Output
- Use parameterized queries (never string concatenation)
- Escape HTML before rendering in browsers
- Encode JSON safely
Secrets Management
Never Hardcode Secrets
# ❌ Bad
API_KEY = "sk-live-abc123"
# ✅ Good
API_KEY = os.environ.get("API_KEY")
Use a Secrets Manager
| Tool | Use Case |
|---|---|
| HashiCorp Vault | Enterprise, complex policies |
| AWS Secrets Manager | AWS-native applications |
| Azure Key Vault | Azure-native applications |
| Doppler | Multi-cloud, developer-friendly |
| 1Password Secrets | Small teams, simple setup |
Dependency Security
Keep Dependencies Updated
# Scan for vulnerabilities
npm audit
pip-audit
snyk test
Lock Files
Always commit lock files (package-lock.json, poetry.lock, Cargo.lock) to ensure reproducible builds.
OWASP Top 10 Prevention
| Vulnerability | Prevention |
|---|---|
| Injection | Parameterized queries, input validation |
| Broken Access Control | Deny by default, enforce ownership |
| Cryptographic Failures | HTTPS everywhere, encrypt at rest |
| Insecure Design | Threat modeling, security requirements |
| Security Misconfiguration | Minimal platforms, remove defaults |
| Vulnerable Components | Dependency scanning, auto-updates |
| Auth Failures | MFA, strong passwords, session limits |
| Software Integrity | Verify packages, signed commits |
| Logging Failures | Log all auth events, monitor anomalies |
| SSRF | Whitelist URLs, disable unnecessary protocols |
Secure Communication
HTTPS Everywhere
- Redirect HTTP to HTTPS
- Use HSTS headers
- Keep TLS certificates up to date
API Security
- Rate limiting (prevent brute force)
- API versioning (graceful deprecation)
- Request signing (verify integrity)
Logging & Monitoring
What to Log
- Authentication attempts (success and failure)
- Authorization failures
- Input validation errors
- Unusual traffic patterns
What NOT to Log
- Passwords
- API keys
- Personal health information
- Credit card numbers
Security Checklist
- Authentication uses MFA where required
- Authorization checks resource ownership
- All inputs validated and sanitized
- Secrets stored in a secrets manager
- Dependencies scanned for vulnerabilities
- HTTPS enforced for all traffic
- Security headers configured (CSP, HSTS)
- Rate limiting enabled on public APIs
- Sensitive data encrypted at rest
- Security events logged and monitored
Troubleshooting
- Authentication bypass in tests: ensure test users cannot reach production endpoints.
- False positives in scanning tools: tune rules against the risk profile. Distinguish between reachable vulnerabilities and theoretical issues.
- Secrets appear in logs: configure log filters to redact tokens, passwords, and keys. Audit log sinks for sensitive patterns.
- CSP breaks legitimate functionality: use report-only mode first, then enforce. Iterate on allowed sources based on real violations.
- Incident response stalls: run tabletop exercises.
Key Takeaways
- Apply security best practices guide when you need a practical solution for your use case.
- Monitor performance after implementation; measure latency, errors, and resource usage before and after.
- Check the Troubleshooting section for common failures; most have documented root causes with fixes.
- Keep dependencies updated and run tests in CI to prevent production regressions.
Advanced Topics
Scenario: Hardening Node.js API for Production
// 1. Helmet: HTTP security headers
const helmet = require("helmet");
app.use(helmet());
// X-Content-Type-Options: nosniff
// X-Frame-Options: DENY
// Strict-Transport-Security: max-age=31536000
// Content-Security-Policy: default-src self
// 2. Rate limiting
const rateLimit = require("express-rate-limit");
app.use("/api", rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // 100 requests per minute
message: "Too many requests"
}));
// 3. Strict CORS
const cors = require("cors");
app.use(cors({
origin: ["https://app.example.com"],
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true
}));
// 4. Input validation (Zod)
const { z } = require("zod");
const userSchema = z.object({
email: z.string().email().max(255),
password: z.string().min(12).max(128),
name: z.string().min(1).max(100).regex(/^[a-zA-Z0-9 ]+$/)
});
app.post("/api/users", (req, res) => {
const result = userSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({ error: "Invalid input" });
}
// ... process
});
// 5. SQL injection prevention (parameterized queries)
app.get("/api/users/:id", async (req, res) => {
// NEVER: `SELECT * FROM users WHERE id = ${req.params.id}`
// ALWAYS: parameterized queries
const result = await pool.query(
"SELECT id, email, name FROM users WHERE id = $1",
[req.params.id]
);
res.json(result.rows[0]);
});
// 6. Secure JWT
const jwt = require("jsonwebtoken");
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: "15m", algorithm: "RS256" }
);
// 7. Audit logging
app.use((req, res, next) => {
const start = Date.now();
res.on("finish", () => {
logger.info({
method: req.method,
path: req.path,
status: res.statusCode,
duration: Date.now() - start,
ip: req.ip,
userId: req.user?.id,
});
});
next();
});
// 8. Dependency scanning in CI
// npm audit --audit-level=high
// npx snyk test
// npx trivy fs .
Which security headers are mandatory?
X-Content-Type-Options: nosniff (prevent MIME sniffing), Strict-Transport-Security: max-age=31536000 (enforce HTTPS), X-Frame-Options: DENY (prevent clickjacking), Content-Security-Policy: default-src self (prevent XSS), Referrer-Policy: no-referrer (minimize exposed info). Use helmet() in Express to configure all of them automatically.
Common Production Pitfalls
- Treating the guide as a checklist to complete once rather than a practice to evolve.
- Adopting every recommendation at once instead of starting with one measured change.
- Skipping the maturity assessment and forcing advanced practices on an unprepared team.
- Not updating runbooks and on-call expectations as new practices are introduced.
- Ignoring real incident data when prioritizing which parts of the guide to apply first.
- Failing to assign an owner who reviews decisions quarterly.
- Copying examples without adapting them to the team’s actual tooling and constraints.
- Forgetting to measure outcomes before adding the next improvement.
Frequently Asked Questions
How often should I update dependencies?
At least monthly. Enable Dependabot or Renovate for automated PRs.
Is JWT secure?
JWT is secure when implemented correctly: short expiry, strong signing algorithms (RS256/ES256), secure secret storage, and HTTPS-only transmission.
Should I encrypt everything in the database?
Encrypt sensitive fields (PII, credentials, tokens). At-rest encryption should be enabled at the database level.
Related Resources
How to Hash Passwords Securely (Python, JavaScript, Java)
Learn how to hash and verify passwords with bcrypt, Argon2, and PBKDF2. Practical examples in Python, JavaScript, and Java, plus migration steps and parameter trade-offs.
RecipeJWT Authentication
How to generate, validate, and refresh JSON Web Tokens for stateless API authentication.
RecipeInput Validation
How to validate user input safely using schemas, type checking, and sanitization across Python, JavaScript, and Java.
DocSecrets Rotation Template
A template for scheduling and tracking the rotation of API keys, tokens, and certificates.
RecipeSSH Key Management in Bash
Generate, rotate, and distribute SSH keys with bash scripts
RecipeSchema-Based Data Validation with Zod in TypeScript
Validate and sanitize incoming data using Zod schemas with TypeScript inference, custom refinements, and error formatting for reliable API and form validation