Skip to content
HANA PlatformHANARAD
Engineering

A practical web application security checklist

The web application security checklist we apply on every project: server-side validation, record-level authorization, password hashing, secure sessions, rate limiting and more.

Author
HANARAD Engineering Team
Published
Updated
Updated
Reading time
6 min read

Most security incidents in business software are not caused by exotic attacks. They come from ordinary gaps: an endpoint that trusts its input, a permission check that confirms the role but not the record, a password stored with a weak hash, an API key committed to a repository. A web application security checklist exists to close those gaps systematically, on every project, regardless of deadline pressure. This is the checklist our developers apply by default, taken from the written engineering standards every project follows.

It is written for founders, CTOs and product owners as much as for developers. If you are commissioning software, you can use it to ask your development partner direct questions. If you are building it, you can use it as a review gate before every release.

1. Validate every input on the server

Frontend validation is a convenience for users. It is not security, because anyone can send requests straight to your API with any payload they like. Every request body, query parameter, path parameter and header that the application relies on must be validated on the server before it touches business logic.

  • Define a schema for each request, for example with Zod, and reject anything that does not match.
  • Validate types, lengths, formats and allowed values, not just presence.
  • Strip or reject unknown fields so attackers cannot set properties such as roles or owner IDs.
  • Share schemas between frontend and backend so both enforce the same rules from one definition.

2. Authorize every endpoint, down to the record

Authentication answers who the user is. Authorization answers what they may do, and it is where many serious breaches happen. The most common flaw is checking that a user has the right role while forgetting to check that they can access the specific record they requested. A sales executive who can view orders should see their own orders, or their team's, not every order in the system just by changing an ID in the URL.

  1. Apply role-based access control guards to every endpoint, with access denied by default.
  2. After loading a record, confirm the user owns it or belongs to the tenant, team or branch that does.
  3. Scope list queries by tenant or owner in the query itself, rather than filtering results afterwards.
  4. Write tests that try to access another user's records and expect a refusal.

Record-level checks are tedious to write and easy to forget, which is exactly why they belong on a checklist and in automated tests.

3. Hash passwords properly

Passwords must never be stored in plain text or with fast general-purpose hashes. Use a modern, memory-hard password hashing algorithm such as argon2, which makes brute-forcing stolen hashes expensive. Never log passwords, never email them, and never include them in error messages or analytics events.

  • Hash with argon2 and a per-password salt, which the library handles automatically.
  • Enforce sensible minimum lengths and check against commonly breached passwords.
  • Offer multi-factor authentication for administrative and high-privilege accounts.
  • Make password reset tokens single-use and short-lived.

4. Use secure sessions, not tokens in local storage

For browser-based applications, session cookies marked httpOnly, Secure and SameSite are the safer default. httpOnly prevents JavaScript from reading the cookie, which limits the damage of a cross-site scripting bug. Secure ensures it is only sent over HTTPS. SameSite reduces cross-site request forgery risk. The session itself lives server-side in Redis or PostgreSQL, so it can be revoked instantly.

Storing access tokens in localStorage is a common shortcut and a poor one, because any script running on the page can read them. Where JWTs are needed, for mobile or third-party clients, keep access tokens short-lived and rotate refresh tokens on every use.

5. Rate limit authentication and public endpoints

Login, registration, password reset, OTP verification and public forms are natural targets for automated abuse. Rate limiting slows credential stuffing, brute-force attempts and spam without affecting genuine users.

  • Limit attempts per IP address and per account identifier on authentication routes.
  • Use a shared store such as Redis so limits work across multiple server instances.
  • Return generic error messages that do not reveal whether an account exists.
  • Monitor and alert on unusual spikes in failed attempts.

6. Set security headers and a strict CORS policy

HTTP security headers instruct browsers to apply protections such as preventing clickjacking, blocking MIME-type sniffing and restricting where scripts can load from. Middleware such as Helmet sets sensible defaults in a few lines. Cross-origin resource sharing should use an explicit allowlist of trusted origins, never a wildcard on endpoints that use credentials.

7. Keep secrets out of code

Database passwords, API keys, signing secrets and third-party credentials belong in environment variables or a secrets manager, never in source code or committed configuration files. Validate required environment variables at startup so a missing or malformed secret stops the application immediately instead of causing confusing failures later.

  • Add secret scanning to the repository and CI pipeline.
  • Use different credentials for development, staging and production.
  • Rotate secrets on a schedule and immediately when someone with access leaves.
  • Give each service only the permissions it needs.

8. Use parameterized database queries

SQL injection remains possible whenever queries are assembled by concatenating strings with user input. An ORM such as Prisma parameterizes queries by default. When raw SQL is genuinely necessary, use tagged template queries that bind values safely rather than building SQL strings by hand. Also select only the fields an endpoint needs, so password hashes and internal flags are never returned by accident.

9. Handle file uploads defensively

File uploads are a frequent source of vulnerabilities, from oversized files that exhaust storage to disguised scripts served back to other users.

  1. Validate file type by content, not only by extension, and enforce size limits.
  2. Upload directly to S3-compatible storage using short-lived pre-signed URLs.
  3. Serve user files from a separate domain, never from the application domain.
  4. Scan uploads where the risk profile justifies it, and never execute uploaded content.

10. Fail safely and log responsibly

Error responses should tell the client what went wrong in general terms without exposing stack traces, SQL errors or internal paths. A consistent error format, such as RFC 9457 problem details, keeps responses predictable. Internally, structured logs with a request ID on every line make incidents traceable, while care is taken never to log passwords, tokens or personal data. Unhandled errors should be reported to an error-tracking service so they are noticed quickly.

The web application security checklist at a glance

Summary of the checklist and the question to ask your team
AreaQuestion to ask
Input validationIs every input validated on the server with a schema?
AuthorizationDoes every endpoint check role and record-level access, denying by default?
PasswordsAre passwords hashed with argon2 and never logged?
SessionsAre browser sessions httpOnly, Secure, SameSite cookies stored server-side?
Rate limitingAre login and public endpoints rate limited across instances?
Headers and CORSAre security headers set and CORS restricted to an allowlist?
SecretsAre all secrets in environment variables and validated at startup?
DatabaseAre all queries parameterized and field selection deliberate?
UploadsAre type and size validated, with pre-signed URLs and a separate domain?
ErrorsAre internal details hidden from clients and logged safely?

Making the checklist stick

A checklist only helps if it is applied every time. That is why we encode these rules into shared project templates, code review standards and automated tests, so that the secure path is also the default path. It is one of the main reasons we standardized on a single stack: when every developer uses the same validation library, the same authorization guards and the same session setup, security does not depend on one person remembering. You can read more about this on our why our tech stack page and see the specific tools on our web and backend stack page.

Security that depends on individual memory will eventually fail. Security built into templates, tests and reviews keeps working.

If you have an existing application and are unsure how it measures up, our software maintenance and support team can review it against this checklist and prioritize fixes. For new builds, these controls are part of every web application development project from the first sprint.

About the author

HANARAD Engineering Team

Engineering & AI practice, HANARAD PLATFORM PRIVATE LIMITED

We are a team of 100+ developers based in Ahmedabad, India, all trained on one standardized web, mobile and AI stack. We write about the trade-offs we see while building and maintaining software for clients.

Published by HANARAD PLATFORM PRIVATE LIMITED · CIN U46512GJ2024PTC157221

FAQ

Questions readers ask

Let's build your next product

Book a free 30-minute consultation. We'll map your goals, suggest the right approach and outline a realistic plan — no obligation.