Backend & API

19 practices · OWASP Cheat Sheet Series (Authentication, Authorization, REST, Injection Prevention)

Server-side controls: authentication and authorization, input handling, storage of credentials, logging, and the write paths where money and permissions change.

Ticking these off for a specific project? The Project Security Checklist tracks all nine categories in one place. For why these patterns keep recurring, see Security Best Practices.

1. Verify Authentication Tokens Properly

Description

Every JWT is verified with the library's verify() call against a pinned algorithm and known key — never decoded and trusted, and never accepting alg:none.

Example
jwt.verify(token, PUBLIC_KEY, { algorithms: ['RS256'] })
// never: JSON.parse(atob(token.split('.')[1]))

2. Enforce Authorization on Every Resource (BOLA/BFLA)

Description

Authentication answers "who are you"; authorization answers "are you allowed to do this to this specific record." Check both, on every single read and write, not just at the route level.

Example
Invoice.findOne({ _id: id, ownerId: session.userId })
// not just Invoice.findById(id)

3. Security Headers & Strict CORS by Default

Description

Apply Helmet's defaults (or equivalent) to every service — CSP, HSTS, X-Content-Type-Options — and replace any wildcard CORS with an explicit origin allowlist, especially wherever credentials:true is set.

Example
app.use(helmet())
app.use(cors({
origin: ['https://app.example.com'], credentials: true
}))

4. Rate Limit Sensitive Endpoints

Description

Login, password-reset, OTP verification, and any endpoint that reveals whether something exists get per-IP and per-account limits with exponential backoff — not just a global API rate limit.

Example
rateLimit({ windowMs: 60_000, max: 5 })
applied specifically to /auth/*, /reset-password

5. Centralized Input Validation

Description

Validate every input against an explicit schema at the API boundary (zod, joi, class-validator) — reject anything that doesn't match, rather than trying to sanitize after the fact.

Example
const schema = z.object({
email: z.string().email(),
amount: z.number().positive().max(100000)
})

6. Parameterized Queries, Always

Description

Never build SQL/NoSQL queries by string concatenation or interpolation — use parameterized queries or an ORM's query builder so user input can never become part of the query structure itself.

Example
db.query('SELECT * FROM users WHERE id = $1', [id])
// never: `SELECT * FROM users WHERE id = ${id}`

7. Secure Password Storage

Description

Hash passwords with a slow, purpose-built algorithm (argon2id or bcrypt with a strong cost factor) and a per-user salt — never MD5/SHA-256 alone, and never a home-grown scheme.

Example
await argon2.hash(password, { type: argon2.argon2id })

8. Verify Webhook Signatures

Description

Every inbound webhook is verified against the provider's signature using the raw request body, with a timestamp check to reject replayed events, before any of its payload is trusted.

Example
if (!verifyHmac(rawBody, sig, WEBHOOK_SECRET))
return res.sendStatus(401)

9. Never Log Secrets

Description

Structured logging with an explicit field allowlist, centralized redaction for known-sensitive patterns, and a lint rule or review gate banning raw request/response body logging.

Example
logger.info('checkout', { orderId, amountCents })
// never: logger.info('checkout', req.body)

10. Audit Logging for Sensitive Actions

Description

Log who did what, when, for every privileged action (role change, data export, refund issued) in an immutable, queryable trail — separate from debug logs, retained long enough to investigate later.

Example
auditLog.record({
actor, action: 'role.change', target: userId,
before, after, timestamp
})

11. Generic Error Handling

Description

Clients get a generic error message and a correlation ID; the full stack trace, query, and internal details go only to server-side logs — verbose errors are a reconnaissance tool for an attacker.

Example
res.status(500).json({
error: 'Something went wrong', requestId: req.id
})

12. Secure Session Management

Description

Rotate the session identifier on login and privilege change, enforce an absolute session timeout in addition to idle timeout, and invalidate all sessions server-side on password change or logout-everywhere.

Example
req.session.regenerate(() => {
req.session.userId = user.id
}) // on every successful login

13. Output Encoding to Prevent Injection

Description

Anything rendered into HTML, a shell command, or a template gets encoded for that specific context — the same string might need different encoding to be safe in HTML versus a URL versus a shell argument.

Example
execFile('convert', [userFilename]) // args array
// never: exec(`convert ${userFilename}`)

14. Secure File Upload Handling

Description

Validate file type by content (not just extension), enforce a size cap, store uploads outside the web root or in object storage with no execute permission, and scan for malware before anything else touches the file.

Example
fileTypeFromBuffer(buffer) // magic-byte check
+ ClamAV scan before moving out of quarantine bucket

15. Dependency Scanning & Patching

Description

Run software composition analysis (Snyk, Dependabot, npm audit) in CI on every PR, and set an SLA for patching known-vulnerable or EOL packages — don't let "we'll upgrade eventually" become the plan.

Example
Dependabot: auto-PR on new CVE, auto-merge patch-level
security updates that pass CI

16. Idempotency for Critical Writes

Description

Any retryable operation that touches money, credits, or inventory uses an idempotency key and an atomic database operation — so a network retry or a double-click can never apply the effect twice.

Example
await db.payments.insertOne({ idempotencyKey, ... })
// unique index on idempotencyKey prevents double-charge

17. Service-to-Service Authentication (mTLS)

Description

Internal service-to-service calls are authenticated too — mutual TLS or short-lived service tokens — so compromising one service doesn't grant free rein to call every other internal API unauthenticated.

Example
Service mesh (Istio/Linkerd) enforcing mTLS between
all pod-to-pod traffic in the cluster by default

18. API Gateway Validation & Quotas

Description

Put request-schema validation, per-client quotas, and version negotiation at the gateway layer, so malformed or abusive traffic is rejected before it reaches application code at all.

Example
API Gateway request validator:
reject unknown fields, enforce max payload size,
per-API-key quota

19. SAST/DAST in CI/CD

Description

Static analysis (Semgrep, CodeQL) runs on every pull request looking for the exact patterns in this deck, and dynamic scanning (OWASP ZAP) runs against staging before major releases.

Example
GitHub Action: semgrep --config=p/owasp-top-ten
on every PR, blocking merge on new findings

Last updated: September 2026 · Source: Offspring — Security Best Practices.pptx (Part 4) + Security Best Practices Checklist.xlsx