Hype StackHypeStack

Search

Search the packs, templates, docs, and pages

Guide

Web App Security Best Practices: A Checklist at Code Level

Most breaches of small apps are not clever. One route skipped the permission check, or a secret ended up in the JavaScript bundle. Both are visible in the code if you know where to look.

Search for web app security best practices and you get lists written for a security team: write a security plan, run a risk assessment, learn the OWASP Top 10. That is sound advice for a company with a security function. It is not much help to the person who wrote the app last month, often with a coding agent, and wants to know whether it is safe to put real users on it.

This page is for that person. The first half is a checklist you can run against any codebase, in any framework. The second half is how a foundation changes the list, where ours stops, and what stays your job either way.

When a checklist is not enough

If the app stores health records, card numbers, or anything a regulator has a name for, a checklist is the start and not the finish. You need someone qualified to review the system, and probably a compliance programme. Nothing on this page, and nothing we sell, substitutes for that.

If the app is a prototype nobody else logs into, most of this can wait until a stranger creates an account.

The checklist, route by route

Each item can be confirmed by reading code.

Nine things to verify before real users arrive

  • Authentication is applied to a router, not to each handlerIf every handler has to remember the session check, one will forget. Mount it once on the router so a new route is protected unless someone opts out on purpose.
  • Every route states what permission it needsBeing logged in is not being allowed. Each write names an action and a subject, next to the route definition where a reviewer sees it.
  • Queries are scoped to the tenant from the sessionThe organization id comes from the server-side session, not from the URL or the body. A record id sent by the client is looked up together with its tenant.
  • Session cookies carry the right flagsHttpOnly so page scripts cannot read them, Secure in production, and a SameSite value you chose on purpose. If the API and the frontend sit on different sites, an origin allow-list has to do SameSite's job.
  • Input is validated where it entersParse the body, query, and params against a schema at the route, and hand the handler the parsed value only.
  • Unauthenticated writes are rate limited and size cappedLogin, signup, password reset, and contact forms accept traffic from anyone. Limit them per IP and cap the request body, or one script can fill your database or your email quota.
  • Webhooks verify a signature over the raw bodyVerify before parsing. Checking the signature against re-serialised JSON fails for real events, and that tempts someone to switch the check off.
  • Secrets stay on the serverAnything a bundler inlines into the frontend is public. Search the built JavaScript for your API keys once, and validate required variables at boot.
  • Dependencies get updated on a scheduleCommit a lockfile, run the package manager's audit in CI, and take auth and payment library updates promptly.

Where authorization goes missing

Broken access control is the failure this list spends three items on, because it is the one that code review misses. The bug is that user A can request /invoices/123 and receive an invoice that belongs to user B, because the handler looked the record up by id and never asked whose it was.

It happens one route at a time. Twenty endpoints get the check, the twenty-first is copied from a handler in a public router, and nothing fails, since a missing check produces a successful response.

Two habits reduce it. Make the unsafe version harder to write than the safe one, by attaching auth to the router and permissions to the route. And write one test per resource that logs in as a member of a different organization and expects a refusal.

What generated code tends to get wrong

Vibe coding security problems are mostly ordinary security problems, produced faster. An agent optimises for the request in front of it, and "make the dashboard load" has no clause about who else can load it.

Patterns worth searching a generated codebase for

Checks in the UI only

The button is hidden for non-admins and the endpoint behind it accepts anyone. The API has to refuse the request on its own.

Ids trusted from the client

An organizationId or userId read from the request body and passed straight into a query. Whoever sends the request chooses whose data they see.

Secrets behind a public prefix

A service key renamed to a VITE_ or NEXT_PUBLIC_ variable so the frontend can call a provider directly. The key now ships to every visitor.

Errors caught and returned

A try/catch around each handler that sends the raw error message to the client. It can leak internals, and it hides the failure from your error reporting.

How a foundation changes the list

A checklist is a list of things to remember. A foundation turns some of them into things you would have to remove on purpose. That is the case for starting from a template, ours or anyone's, and the reason to read its code first.

In Hype Stack the pieces below are source files in your repository after install.

Where each check lives after installing the SaaS starter and the Stripe billing pack

Auth on the router

getAuthenticatedApp() in apps/backend/src/utils/auth/auth-hono.ts returns a Hono app with the session middleware attached. It re-reads the user row from Postgres and takes the organization id from the session, not the request.

withAuthUsersession
Permission per route

hasAccess({ action, subject }) in apps/backend/src/middleware/permissions/has-access.ts answers 403 when there is no user, no organization, or the role's ability does not allow the action.

hasAccessCASL
Validation at the boundary

Routes take input through validate() with a Zod schema. Environment variables are parsed against a Zod schema at startup, so a missing secret stops the boot.

zodvalidateEnv
Webhook signature

The Stripe pack's /checkout/webhook route reads the body as text and passes it to stripe.webhooks.constructEvent with STRIPE_WEBHOOK_SECRET before anything else runs. The handler then re-syncs that customer's state from Stripe, so a replayed event ends in the same place.

constructEventidempotent sync
Errors in one place

Handlers throw typed errors and a central error middleware formats the response. The shipped rules files tell a coding agent to throw instead of wrapping handlers in try/catch.

error middlewarerules files

Paths are where the files land in a generated project. The base template is open source; the starter and billing packs are paid.

One detail to know before you deploy. The Better Auth starter sets session cookies to SameSite=None; Secure in production, because the frontend and the API are expected to live on different domains. Cross-site protection then comes from the trustedOrigins list built from FRONTEND_URL and ADMIN_URL. If you host both apps under one site, tightening the cookie to Lax in apps/backend/src/libs/auth/auth.ts is a one-line change you own.

What is not in the box

The template and packs ship no API rate limiting. Our own docs say so. Login, signup, password reset, and the webhook route accept as many requests as your host will carry until you add a limit, either at a reverse proxy or edge in front of the backend, or as middleware on the sensitive routes. Hono has a body limit middleware for the size cap; the docs describe no global cap of ours.

The Better Auth starter also ships with requireEmailVerification: false, so a new account can sign in before the address is confirmed.

There is also no web application firewall, dependency scanner, or secret scanner. Those are other people's products with their own bills. We make no compliance claim.

What you still write

The foundation covers the checks that look the same in every app. The ones specific to yours remain:

  • A hasAccess line on every route you add, and a decision about which role gets which action.
  • Tenant scoping in your own queries. The middleware hands you an organizationId. Using it in each where clause is your code.
  • The cross-organization test for each new resource.
  • Rate limits and body caps, as above.
  • Rotation and storage of production secrets in your host's secret manager.

If you build with an agent, put those rules where it reads them. The template's rules files already cover validation and error handling. A line about tenant scoping is yours to add.

Below is the stack this page describes, with its screens and pack list. Read the middleware files first, then compare them with the checklist.

Hype Stack

What is Hype Stack?

Every product starts with the same month of work nobody pays you for: sign-up and login, teams and permissions, taking payments, notifications, an admin panel to run the business. Hype Stack is that month, already built and tested. Start from the free open-source app, add the pieces you need with one command, and keep going on the part that is actually your idea.

Everything lands as real code in your own repository, so there is nothing to rent and nothing anyone can switch off. For the engineers: React 19, Hono, Postgres, and a desktop build, typed end to end.

See it running

Templates are curated project starters built on this stack: a layout, feature packs, a custom theme, and bonus pages. The previews below are recordings of the real apps.

Packs in this stack

Every pack ships real source code: frontend, backend, and admin surfaces where the feature needs them. The ones this stack installs come first; the rest can be composed in later.

Two ways to install features

Same features underneath, different starting point. Either command resolves what the packs depend on, copies the source into your repository, and merges the Prisma schema.

Ready-made

Take a template

A landing page, a design system, a themed layout, and the features already wired into it. Rebrand it, put your product in the middle, ship.

$npx @hype-stack/cli template
Browse templates
From scratch

$ hype-stack compose

✓ Auth✓ Payments

Compose your own design

Your design and your choices, without rebuilding auth, billing, or notifications. Tick the packs you want and the CLI wires them into the open-source starter.

$npx @hype-stack/cli compose
Browse packs

Questions, answered

Broken access control: a route that checks the user is logged in but not whether the record belongs to them or their organization. It produces a normal successful response, so nothing fails in testing. Attach auth to the router, a permission to each route, and test with a user from another organization.

More stacks

Turn your ideas into
Real applications.

Start free and own every line you ship. When you want more, one All-Access license unlocks every premium pack and template for a year.

All premium packsEvery template12 months of updates
Get the whole catalog$299/year