I launched my first app without proper security. Within 48 hours, someone discovered my unprotected API endpoint and exported my entire user database. No passwords were stolen (they were hashed, thank god), but I learned a brutal lesson: Security isn't a feature you add later. It's a foundation you build on. Skip it and you're building a house with no locks.
Risk Radar: The 'I'll Add Security Later' Trap
The Mistake: Thinking "my app is too small to be a target" or "I'll add proper security before launch." Bots scan for vulnerabilities 24/7. They don't care how many users you have.
The Reality: Security breaches happen to apps with 5 users and apps with 5 million users. The difference is that secured apps survive, unsecured apps become cautionary tales.
The Fix: Build security in from day one. It's easier to start secure than to retrofit it later.
LLM Conversation Starter
Security audit your architecture:
I'm building [your app description] with [your tech stack]. Security audit my current setup: 1. What are my biggest security vulnerabilities right now? 2. Which security issues would prevent me from launching? 3. What's the minimum security checklist for going live? 4. How do I protect my API endpoints? 5. What should I do about password storage? Be brutally honest. I'd rather fix it now than after a breach.
Why this works:
Your LLM will identify vulnerabilities you didn't know existed. This is preventive medicine for your app's security.
Launch-Blocking Security Issues
These aren't "nice to have" - these are dealbreakers. If you don't have these in place, DO NOT launch. I'm serious. Delay your launch if you have to.
1. HTTPS Only (No Exceptions)
HTTP sends data in plain text. Anyone between your user and your server can read it - passwords, credit cards, everything. HTTPS encrypts this data.
✅ How to enable:
- • Vercel/Netlify: HTTPS is automatic and free
- • Custom domain: Let's Encrypt (free, automatic)
- • Force HTTPS: Redirect all HTTP to HTTPS
Check: Your URL should show a lock icon (🔒) in the browser. If not, you're not using HTTPS.
2. Password Hashing (Never Plain Text)
Storing passwords in plain text is a fireable offense at real companies. Even YOU shouldn't be able to read user passwords. Use bcrypt or similar hashing.
❌ NEVER Do This:
// Storing password directly user.password = "password123" // Anyone with database access // can see passwords
✅ Always Do This:
import bcrypt from 'bcrypt' const hash = await bcrypt.hash( password, 10 ) user.password = hash // Stored: $2b$10$N9qo8...
✅ Better: Use NextAuth.js or similar - they handle this for you.
3. Environment Variables (No Secrets in Code)
Every secret (database password, API keys, auth tokens) must be in environment variables, NEVER in your code. If it's in your code, it's in GitHub, and that means it's public.
❌ Commits that will haunt you:
const dbPassword = "supersecret123" // ← Everyone can see this const stripeKey = "sk_live_..." // ← You just exposed your Stripe account
✅ The safe way:
// .env (NEVER committed to GitHub) DB_PASSWORD=supersecret123 // In your code const dbPassword = process.env.DB_PASSWORD
Security Alert: GitHub Scanning
Did you know? Bots scan every public GitHub commit for exposed secrets within minutes. They automatically try to use them.
If you accidentally commit a secret:
- Rotate/change the secret IMMEDIATELY (not after you fix the commit)
- Remove it from Git history (not just delete the file)
- Assume it's been compromised
Prevention: Use git-secrets or similar tools to block commits with secrets.
Input Validation (Trust Nothing)
Users will input things you never imagined. Some by accident, some maliciously. Your job: validate EVERYTHING before it touches your database.
The Input Validation Commandments
1. Never Trust Frontend Validation
Frontend validation is for UX, not security. Anyone can bypass it with browser dev tools. ALWAYS validate on the backend too.
// Backend validation (required)
if (!email || !email.includes('@')) {
return { error: 'Invalid email' }
}
if (password.length < 8) {
return { error: 'Password too short' }
}2. Sanitize User Input
Users (or bots) will try to inject code. Strip out dangerous characters before saving.
⚠️ Attack example:
// User enters in "name" field:
<script>alert('hacked')</script>
// If you don't sanitize, this runs on other users' browsers✅ Protection:
import DOMPurify from 'dompurify' const clean = DOMPurify.sanitize(userInput)
3. Use Parameterized Queries
SQL injection is still the #1 web vulnerability. Never build SQL queries with string concatenation.
❌ SQL Injection Vulnerable:
const query = "SELECT * FROM users WHERE id = " + userId // Attacker sends: 1 OR 1=1 // Gets all users!
✅ Protected:
// Prisma/ORM does this for you
const user = await prisma.user
.findUnique({
where: { id: userId }
})
// Automatically sanitized4. Rate Limit Everything
Prevent bots from spamming your endpoints. Limit how many requests one IP can make per minute.
Protection levels:
- • Login: 5 attempts per 15 minutes
- • Password reset: 3 attempts per hour
- • API calls: 100 requests per minute
- • Account creation: 1 per IP per hour
PM Insight: The Security-UX Balance
Professional PMs know: Too much security frustrates users. Too little exposes them to risk. The sweet spot:
- • Invisible security (rate limiting, input sanitization) - always on
- • Light friction (password requirements) - users understand
- • Heavy friction (2FA, captcha) - only for sensitive actions
Don't make users solve a captcha to view public content. Do make them solve one before sending 100 password reset emails.
API Endpoint Protection
Every API endpoint is a door into your system. Some should be open to the public. Most should be locked. Here's how to decide and implement:
The Three Types of Endpoints
1. Public (No Auth Needed)
Anyone can access, no login required
- • GET /api/posts (viewing public content)
- • GET /api/products (browsing items)
- • POST /api/contact (contact forms)
2. Authenticated (Login Required)
Must be logged in
- • GET /api/user/profile (viewing own data)
- • POST /api/posts (creating content)
- • PUT /api/user/settings (updating own settings)
3. Authorized (Permission Required)
Must be logged in AND have specific permissions
- • DELETE /api/users/:id (admin only)
- • POST /api/admin/settings (admin only)
- • PUT /api/posts/:id (must be post owner or admin)
Implementation Pattern
Public endpoint (no protection):
export async function GET() {
const posts = await db.post.findMany()
return Response.json(posts)
}Authenticated endpoint (check session):
export async function POST(req: Request) {
const session = await getServerSession(authOptions)
if (!session) {
return Response.json(
{ error: 'Unauthorized' },
{ status: 401 }
)
}
// User is logged in, proceed
const data = await req.json()
// ... create post
}Authorized endpoint (check permission):
export async function DELETE(
req: Request,
{ params }: { params: { id: string } }
) {
const session = await getServerSession(authOptions)
if (!session) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
const post = await db.post.findUnique({ where: { id: params.id } })
// Check if user owns this post OR is admin
if (post.authorId !== session.user.id && !session.user.isAdmin) {
return Response.json({ error: 'Forbidden' }, { status: 403 })
}
// User has permission, proceed
await db.post.delete({ where: { id: params.id } })
}Security Alert: The Authorization Mistake
Common error: Checking if user is logged in, but NOT checking if they have permission to access THAT SPECIFIC resource.
Example vulnerability:
GET /api/user/123/private-messages
→ User 456 is logged in, so they can access it ❌
The fix: Always check: "Is this user allowed to access THIS SPECIFIC data?"
Launch Blocker Checklist
DO NOT LAUNCH until every item is checked:
If you skip security:
- • You WILL be hacked (it's when, not if)
- • Your users' data WILL be exposed
- • Your reputation WILL be destroyed
- • You MAY face legal consequences
Better to launch 2 weeks late with security than on time without it.
Security Resources for Non-Coders
OWASP Top 10
The 10 most critical web security risks, explained in plain English. Read at: owasp.org/www-project-top-ten/
Have I Been Pwned
Check if email addresses/passwords have been in data breaches. Use the API to prevent users from choosing compromised passwords.
Security Headers
Test your site's HTTP security headers at securityheaders.com Aim for an A grade before launching.
"Security is not a product, but a process."
– Bruce Schneier, Cryptographer
You'll never be 100% secure, but being 90% secure puts you ahead of 90% of apps.