Skip to content
All insights

security//3 min read

A security checklist for early-stage web apps

You do not need a security team to avoid the common breaches. You need a handful of habits applied consistently: safe auth, server-side authorization, validated input, careful uploads, and secrets kept out of the repo.

Digitat engineeringProduct studio, Mumbai
Cybersecurity defense shield, encryption padlock, and web application security auditing

In short

  • Most early breaches come from missing authorization checks, not clever exploits.
  • Check permissions on the server for every request; the UI hiding a button is not security.
  • Validate all input at the edge of your system and treat uploaded files as hostile.
  • Keep secrets out of the repository, rotate them, and know how you would respond to a leak.

Security advice tends to swing between the overwhelming and the vague. For a young product, a short list of habits covers most of the real risk. These follow the shape of the OWASP Top 10, trimmed to what matters first.

Authentication

  • Hash passwords with a slow, salted algorithm such as bcrypt, scrypt or Argon2. Never store or log them in plain text.
  • Keep sessions in cookies marked HttpOnly, Secure and SameSite=Lax (or Strict), so scripts cannot read them and other sites cannot easily send them.
  • Rate-limit sign-in, sign-up and password reset, and avoid revealing whether an email address has an account.
  • Offer two-factor authentication for admin accounts at the very least.

Authorization: the one most often missed

The most common serious bug in young apps is not a sophisticated attack. It is an endpoint that loads a record by its ID without checking that the current user may see it. Change /api/invoices/41 to /api/invoices/42 and you are reading someone else's invoice.

ts
// Every query that loads user data is scoped to the caller.
const invoice = await db.query.invoices.findFirst({
  where: and(eq(invoices.id, id), eq(invoices.teamId, session.teamId)),
});
if (!invoice) return notFound(); // same answer for "missing" and "not yours"

Check permissions on the server for every request, in one shared place. Hiding a button in the interface is a convenience, not a control.

Input validation

Validate every request body, query string and webhook payload against a schema at the boundary, and reject what does not match. Use parameterised queries or a query builder so values are never concatenated into SQL. When rendering user content, let your framework escape it, and sanitise any rich text against an allow-list of tags and attributes.

File uploads

  • Check the file's real type from its first bytes, not the extension or the browser's claim.
  • Set a size limit and reject anything larger before reading it all into memory.
  • Do not accept SVG or HTML uploads unless you sanitise them; both can carry scripts.
  • Serve uploads with X-Content-Type-Options: nosniff, ideally from a separate domain or with a restrictive Content Security Policy.

Security headers

A few headers close whole classes of attacks. In Next.js they can be set once:

ts
// next.config.ts
const securityHeaders = [
  { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
  { key: "X-Frame-Options", value: "DENY" },
  { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
];

export default {
  async headers() {
    return [{ source: "/(.*)", headers: securityHeaders }];
  },
};

Add a Content Security Policy next. Start in report-only mode, fix what it reports, then enforce it.

Secrets and dependencies

  • Keep secrets in your platform's environment settings, never in the repository. Add .env* to .gitignore on day one.
  • Use separate keys for development and production, with the least access each needs.
  • Turn on automated dependency alerts and update regularly; most real-world exploits target known, already-patched bugs.
  • Know how you would rotate every key in an afternoon if one leaked.

Operations

  • Automated daily backups, plus a restore you have actually tested.
  • Logs for sign-ins, permission changes and admin actions, kept long enough to investigate.
  • A public way to report vulnerabilities, such as a security.txt file and a monitored address.
Most breaches are not clever. They are a missing check that nobody noticed.

The checklist

  • To do: Passwords hashed with bcrypt, scrypt or Argon2
  • To do: Session cookies HttpOnly, Secure and SameSite
  • To do: Rate limits on sign-in, sign-up and reset
  • To do: Authorization checked on the server for every request
  • To do: Every input validated against a schema
  • To do: Uploads type-checked, size-limited and served safely
  • To do: Security headers and a Content Security Policy
  • To do: Secrets out of the repo, with a rotation plan
  • To do: Dependency alerts on
  • To do: Backups tested by restoring them

Tags

  • Security
  • Web Apps
  • Authentication
  • OWASP

Share

LinkedInX
Written byDigitat engineeringProduct studio, Mumbai

Have a product worth writing about?

Tell us where it is today. We reply within 12 hours.