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
DescriptionEvery 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.
Examplejwt.verify(token, PUBLIC_KEY, { algorithms: ['RS256'] })
// never: JSON.parse(atob(token.split('.')[1]))
2. Enforce Authorization on Every Resource (BOLA/BFLA)
DescriptionAuthentication 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.
ExampleInvoice.findOne({ _id: id, ownerId: session.userId })
// not just Invoice.findById(id)
3. Security Headers & Strict CORS by Default
DescriptionApply 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.
Exampleapp.use(helmet())
app.use(cors({
origin: ['https://app.example.com'], credentials: true
}))
4. Rate Limit Sensitive Endpoints
DescriptionLogin, 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.
ExamplerateLimit({ windowMs: 60_000, max: 5 })
applied specifically to /auth/*, /reset-password
5. Centralized Input Validation
DescriptionValidate 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.
Exampleconst schema = z.object({
email: z.string().email(),
amount: z.number().positive().max(100000)
})
6. Parameterized Queries, Always
DescriptionNever 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.
Exampledb.query('SELECT * FROM users WHERE id = $1', [id])
// never: `SELECT * FROM users WHERE id = ${id}`
7. Secure Password Storage
DescriptionHash 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.
Exampleawait argon2.hash(password, { type: argon2.argon2id })
8. Verify Webhook Signatures
DescriptionEvery 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.
Exampleif (!verifyHmac(rawBody, sig, WEBHOOK_SECRET))
return res.sendStatus(401)
9. Never Log Secrets
DescriptionStructured 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.
Examplelogger.info('checkout', { orderId, amountCents })
// never: logger.info('checkout', req.body)
10. Audit Logging for Sensitive Actions
DescriptionLog 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.
ExampleauditLog.record({
actor, action: 'role.change', target: userId,
before, after, timestamp
})
11. Generic Error Handling
DescriptionClients 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.
Exampleres.status(500).json({
error: 'Something went wrong', requestId: req.id
})
12. Secure Session Management
DescriptionRotate 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.
Examplereq.session.regenerate(() => {
req.session.userId = user.id
}) // on every successful login
13. Output Encoding to Prevent Injection
DescriptionAnything 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.
ExampleexecFile('convert', [userFilename]) // args array
// never: exec(`convert ${userFilename}`)
14. Secure File Upload Handling
DescriptionValidate 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.
ExamplefileTypeFromBuffer(buffer) // magic-byte check
+ ClamAV scan before moving out of quarantine bucket
15. Dependency Scanning & Patching
DescriptionRun 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.
ExampleDependabot: auto-PR on new CVE, auto-merge patch-level
security updates that pass CI
16. Idempotency for Critical Writes
DescriptionAny 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.
Exampleawait db.payments.insertOne({ idempotencyKey, ... })
// unique index on idempotencyKey prevents double-charge
17. Service-to-Service Authentication (mTLS)
DescriptionInternal 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.
ExampleService mesh (Istio/Linkerd) enforcing mTLS between
all pod-to-pod traffic in the cluster by default
18. API Gateway Validation & Quotas
DescriptionPut 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.
ExampleAPI Gateway request validator:
reject unknown fields, enforce max payload size,
per-API-key quota
19. SAST/DAST in CI/CD
DescriptionStatic 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.
ExampleGitHub 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