What you need before you launch an App
The full set of things worth having in place before you go live, grouped by what each one protects, with what breaks when it is missing. Some of it will not apply to your stack. The last two sections cover how to tell, and what order to work in.
Launch guides stop at the parts every app shares, and the items that cost you real money sit outside them. This is the fuller list, around eighty checks across secrets, access, the browser, abuse, payments, model spend, deployment, user data, the legal pages, the build, and the non-security items that sink a launch anyway. It ends with the two answers people give too easily, and the order to work through it all.
How to read this
Most launch advice is made for one type of app. In a different app, many of those points may not apply.
So this list is grouped by what each item protects. The second column is what goes wrong without it. Read that first. If the consequence cannot happen in your app, skip the row.
No security background needed. Unfamiliar terms are in the glossary on this page.
Secrets and configuration
A leaked key makes everything else on this page pointless. Aim for one copy of each secret, held by a secrets manager, so rotating it is one change.
| Check | What happens without it |
|---|---|
| No key reaches the browser bundle | Anyone opens dev tools and reads it. |
| No key in the repository or its history | It stays readable in every clone, forever. |
| Keys come from a manager at run time | Rotation means finding every copy, and you miss one. |
| Separate keys for development and production | A laptop leak becomes a production incident. |
| Anything already committed is rotated, not deleted | The old value still works. |
| Missing config stops the process in production | A blank setting falls back to the permissive default. |
Who can call it, and whose record it is
Most data leaks happen for two reasons.
- The endpoint does not check who is calling.
- It checks the user, but not whether they own the data.
| Check | What happens without it |
|---|---|
| Every private endpoint checks the caller | An unauthenticated request returns real data. |
| Ownership checked per record, not per route | A user changes an id in the URL and reads another account. |
| Admin routes check a role as well as a login | Any signed-up user reaches the admin surface. |
| The same check on write as on read | They cannot see the record but can still edit it. |
| Sessions expire, and rotate on privilege change | A stolen identifier stays useful for months. |
| Cookies set secure, httpOnly and sameSite | The session is readable by script, or sent across sites. |
| Second factor on admin accounts | One reused password owns your system. |
| Logout is a POST and truly ends the session | Any page can log your users out, and the old identifier still works. |
| Email changes confirmed at both addresses | One session becomes a permanent account takeover. |
| A recovery path that survives the login provider | Their OAuth outage locks every user out, including you. |
Change the session ID after login to prevent session fixation. Do this whenever permissions change, not just at login.
If the database uses row-level security, it can handle ownership automatically. If access is handled in the API, you need to check ownership in every query.
Both work. The API approach is easier to get wrong.
Input, output and the browser
Client-side validation is only for convenience. It can be skipped easily. Always enforce important rules on the server too.
| Check | What happens without it |
|---|---|
| Server-side validation of every input | Your client rules are decoration. |
| Fields the client should not set are ignored | A signup carries an admin role, or a checkout carries its own price. |
| Responses carry only what the UI shows | The fields you never render are still readable in the JSON. |
| Output escaping on anything a user typed | Stored content runs as script for every reader. |
| A content security policy | One injected script has the whole page. |
| CORS limited to your own origins | Any site can call your API from a logged-in browser. |
| CSRF tokens on cookie-authenticated writes | A link in an email acts as whoever clicks it. |
| Upload size and type limits, stored off your domain | Your storage bill, or an executable served from your origin. |
| Redirect targets checked against an allowlist | Your login flow forwards users to an attacker page. |
Escaping prevents XSS. Your framework does it by default, except where you disable it. Search for those places and check that the content is safe.
Abuse and the cost that comes with it
This group does not protect data. It protects your money.
- Rate limits on anything expensive: Search, exports, email, model calls, image work. Rate limiting also saves you from an honest client stuck in a retry loop, which is the more common outage.
- Bot protection on public forms: Signup, login, contact, password reset. Turnstile, hCaptcha and reCAPTCHA all do it, and invisible modes cost real users nothing.
- Know why you need both: A rate limit is keyed on the caller, usually an IP address, and proxies sell a fresh one per request. Bot protection asks whether a person is there at all.
- A hard spend ceiling you can see: Per user and per day, failing closed. An alert only tells you it already happened.
- Email and invite flows throttled: Otherwise your domain sends the spam and your sending reputation pays.
- A flag to turn a feature off: Faster than a deploy when something is being abused right now.
If it takes payments
This is the one place a quiet bug moves your money to someone else. All of it comes down to not trusting what arrives.
- Verify the webhook signature, always: Check the HMAC signature against your secret. Make it fail closed, so a missing secret rejects rather than skips. Compare in constant time, because a compare that stops at the first wrong byte leaks the answer.
- Confirm the amount on your server: The client tells you what it thinks it paid. Check amount and currency against the order first.
- Make activation repeat-safe: Providers retry, so granting access has to be idempotent. Otherwise one event twice grants two subscriptions.
- Check refunds before granting: Pay, use, refund, repeat is a free account with extra steps.
- Store nothing you do not need: Card details belong with the provider. What you keep is what you can lose.
If it calls a language model
Model endpoints have the two worst properties together, a price per call and free-form text as input.
- Mark untrusted content as untrusted: A CV or a web page pasted into a prompt is data, not instruction. Wrap it in delimiters and say so in the prompt. This is the basic defence against prompt injection, and it is a filter rather than a wall.
- Never let output act on its own: If a reply can trigger a tool, a query or an email, your code decides whether it runs.
- Cap tokens per request and per user: Both directions. A long input costs you, a long output costs more.
- Log the input, redacted: A wrong answer needs the exact input. It does not need the personal data inside it.
- Name the provider in your privacy policy: Sending user text to them makes them a data processor. Most policies name the company and forget this.
Where it runs
The app can be correct and the thing serving it still hands data to the wrong person.
- HTTPS everywhere, redirect done right: Behind a proxy, redirect on the forwarded protocol header, or you get a loop.
- Caching and authentication together: A cached response served to the wrong person skips every check that made it. If the response depends on a cookie or token, name that in the Vary header. Otherwise your cache is an authentication bypass.
- A health check that means something: One that touches the database, not one returning ok from memory.
- Deploys that roll back by themselves: Check health after release, revert on failure. The fastest fix is the previous release.
- No editing files on the live server: One typo takes it down, and git has no record of what changed.
- Backups you have restored once: An untested backup is a belief.
- Migrations rehearsed on a copy of production: A migration that is fast on a hundred rows can lock the table on a million.
- Indexes on what you query, and a sized connection pool: Both fail the same way, fine in testing and unusable on launch day.
- Error tracking and uptime alerts: Otherwise your users are your monitoring, and they mostly just leave.
Users and their data
These become bigger problems later. They are cheap to fix now but harder once you have real users.
- Export and delete an account: Build deletion first. Retrofitting it across a live schema full of foreign keys is the hard version.
- Privacy flags fail safe: Check for a falsy value, not an explicit false, so a missing field reads as opted out.
- Unsubscribe without a login: A signed token in the link, with an expiry. Anything needing a session becomes a spam complaint.
- Logs redact tokens, cookies and personal data: Otherwise your log store is the easiest place to steal a session.
- Errors say what failed, not how: A stack trace on screen hands over your file layout and library versions.
- A record of what you store and why: One table of each kind of personal data, where it lives, how long you keep it, who else sees it. Every legal question below is answered from it.
The legal side, and the pages that go with it
Which law applies is decided by where your users are, not where you are. One signup from Germany or Bengaluru brings that country rules with it. A small user count exempts you from nothing.
Three regimes cover most of what a new app meets. They ask for similar things. They differ on one point, which direction consent runs in.
- GDPR, for the EU and UK: Write down a lawful basis for each kind of data you hold. Publish what you collect and why. Make access, correction, export and deletion work. Consent is opt-in, so analytics and marketing cookies wait until the user agrees. A breach goes to the regulator within seventy-two hours.
- DPDPA, for users in India: Notice and consent, offered in English and the scheduled Indian languages, with a named grievance contact. Access, correction and erasure mirror GDPR. The part people miss is age. Under eighteens need verifiable parental consent, and no behavioural ads to them. The rules are still phasing in, so check the current position.
- CCPA and CPRA, for California: CCPA has thresholds, roughly twenty-five million dollars of revenue, or data on a hundred thousand people, or half your revenue from selling it. Most small apps are under all three. If you are in scope, it works the other way round to GDPR. Collection is allowed and the user opts out, and you must honour the Global Privacy Control signal automatically.
Brazil, Canada and Australia follow the same pattern with different names. Satisfy GDPR and you are close to all of them, which is why it is the cheapest baseline.
The pages to have before the first signup
- Privacy policy: What you collect, why, how long, who else sees it, and how to exercise rights. List every data processor, your host, email sender, analytics, payment provider and model API. Write it from your own record, not a template.
- Terms of service: What the service is, what users may not do, what happens on suspension, where disputes are heard. Also the only place you limit your liability.
- Cookie notice, banner only if needed: A banner is for non-essential storage. If you set only a session cookie, say so and skip it.
- Refund and cancellation policy: Payment providers want this before approving your account, and Indian ones check for it specifically.
- Contact details that reach a human: GDPR wants an identifiable controller, DPDPA a named grievance contact. A form with nothing behind it satisfies neither.
Where the code and the policy disagree
- The banner that fires nothing: Analytics loads on page load and consent is recorded after. The tracking already happened. Load the script from the accept handler.
- Delete that only hides: A deleted flag is not deletion. Decide what is erased, what is anonymised, and what you keep for tax reasons, then say that in the policy.
- No retention period anywhere: Keeping everything forever is a decision you never made. Put a number on each kind of data and let a job enforce it.
- Age never asked: Under DPDPA, under eighteens need parental consent. American COPPA draws its line at thirteen.
- No breach plan: Seventy-two hours is not long enough to work out who to tell. One page naming who decides and who writes is enough.
This is an engineering summary of what these laws ask software to do, current as of September 2026. It is not legal advice. Thresholds and deadlines move, and anything with real revenue or health, financial or children data behind it is worth an hour with a lawyer.
The build and what it pulls in
Your supply chain is all the packages, images, and CI actions you use but didn't write. They can run with your build permissions.
| Check | What happens without it |
|---|---|
| CI actions pinned to a commit, not a tag | A tag gets moved and new code runs in your pipeline. |
| Lockfile committed, installs are clean | Two builds of the same commit differ. |
| A vulnerability audit that fails closed | A green tick meaning the scanner could not run. |
| Build secrets scoped to the job that needs them | Every step can read your deploy key. |
| Tests and lint block the merge | A gate everyone learns to ignore. |
| Source maps and debug endpoints off in production | Your unminified code and internal routes are public. |
Not a security issue, but it can still kill the launch
People still need to reach your app, use it, and tell you when it breaks. That has its own list.
- Email that actually arrives: Set SPF, DKIM and DMARC records on your domain. Without them your signup and reset mail goes to spam, and you will read that as nobody signing up.
- Certificate and domain renew themselves: Both expire. Both take the whole site down. Both are a calendar reminder away from never happening.
- It works with a keyboard and a screen reader: Labels on inputs, visible focus, real contrast, alt text. The EU Accessibility Act has applied to consumer digital services since June 2025, so this is now a legal item too.
- It works on a mid-range phone: Test on a real device on a slow connection. Most launches are seen there first.
- Search engines can read it: A sitemap, a robots file, real page titles, canonical URLs and link previews. A single-page app with no server rendering often ships with none of it.
- A support address a human reads: Plus a way to report abuse if users can post anything.
- A staging environment with no real user data: Test on a copy with the personal fields scrambled. Copying the live database to staging is a breach you did to yourself.
- Analytics that answer one question: Did anyone come back. Everything else is decoration in week one.
- The business side of payments: A registered entity, tax handled, and invoices that carry the right numbers. Payment providers ask for it before they approve you.
- Someone can reach you when it breaks: An alert going to an inbox nobody opens is the same as no alert.
Not applicable, and probably fine
Two answers come too easily on a list like this.
The first is not applicable. Sometimes true, when your architecture covers the risk elsewhere. Social login only, and password rules govern nothing.
Mark an item not applicable only when you can name what replaces it. If you cannot point at where, it is a gap wearing a friendlier label.
The second is probably fine. That belongs to the items decided route by route rather than in one shared place. Usually two, the fields a client may set and the fields a response returns. Middleware you can read once is verified. Forty routes each deciding for themselves is not.
Closing that is a boring afternoon. List every route that writes data or returns a record, check them one at a time. The one or two that fail are never the ones you would have guessed.
The order to do it in
Trying to do everything at once is how the work gets dropped. Follow this order and stop whenever needed.
- Anything returning another user data. Authentication on every private route, then ownership on every record.
- Anything with a price attached. Payment verification, spend caps, upload limits.
- Secrets. Out of the bundle, out of the repository, into a manager, and rotate what was already committed.
- The browser group. Escaping, CORS, CSRF, redirect targets.
- Abuse. Rate limits, then bot protection on public forms.
- The route sweep for client-set fields and over-full responses.
- Backups, rollback, health checks, error tracking.
- Reachability. Email records, certificate renewal, accessibility, a support address.
- Logs, error messages, dependency pinning, debug surfaces off.
- The legal pages, and the consent behaviour that has to match them.
Two habits matter more than the list itself.
- Write down what you marked not applicable and why. This helps when the architecture changes later.
- When you fix a real issue, add a test or lint rule so it doesn't happen again. Don't just put it in a document nobody reads.
None of this is a certificate. It tells you which risks your architecture handles, which it moved elsewhere, and which one you simply have not done. Most apps have exactly one of the last kind, and it is rarely the one they expected.