Part 2: Web App Security Basics Every Non-Developer Must Know
🔐 Security Isn't Optional—It's Survival
You've built your app. Users are signing up. Revenue is flowing.
Then one morning, you wake up to this email:
"We noticed unusual activity in your database. All user records have been accessed by an unknown party."
This isn't hypothetical. It happens to startups every week. And in most cases, the vulnerability was embarrassingly simple to prevent.
In Part 1, we covered the deployment checklist. Now let's dive deep into the security items that can make or break your business.
🚨 The 5 Most Common Security Mistakes
1. Exposing API Keys in Frontend Code
The Problem: When you build with AI tools, they often generate code like this:
// ❌ DANGER: This key is visible to EVERYONE const supabase = createClient( 'https://abc.supabase.co', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6...', );
Why it's dangerous: Anyone can open Chrome DevTools → Sources tab and see this key. With it, they can:
- Read all your data
- Delete all your data
- Impersonate any user
The Fix:
- Use environment variables:
// ✅ SAFE: Key is loaded from environment const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY, );
- Understand the difference:
| Key Type | Safe in Frontend? | Use Case |
|---|---|---|
anon key | ✅ Yes (with RLS) | Client-side queries |
service_role key | ❌ NEVER | Server-side only |
💡 Rule of thumb: If a key starts with
service_role, it should NEVER appear in frontend code.
2. Disabled Row Level Security (RLS)
The Problem: Supabase makes it easy to disable RLS during development. Many founders forget to turn it back on.
-- This is what "RLS Disabled" looks like -- Anyone can SELECT * FROM users and get EVERYTHING
The Real-World Impact:
- User emails, passwords (hashed, but still), personal data—all exposed
- Competitors can scrape your entire database
- GDPR/CCPA violations = massive fines
The Fix:
- Enable RLS on every table:
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
- Create policies that make sense:
-- Users can only read their own data CREATE POLICY "Users can view own data" ON users FOR SELECT USING (auth.uid() = id); -- Users can only update their own data CREATE POLICY "Users can update own data" ON users FOR UPDATE USING (auth.uid() = id);
- Test it:
-- Try to select without being logged in -- Should return 0 rows if RLS is working SELECT * FROM users;
3. No Server-Side Validation
The Problem: AI-generated apps often validate inputs only on the frontend.
// Frontend validation only ❌ if (price < 0) { alert("Price can't be negative!"); return; } // Hacker opens DevTools, modifies the request, bypasses this entirely
The Fix: Always validate on the server. Frontend validation is for UX, not security.
// Supabase Edge Function example ✅ export async function POST(req) { const { price } = await req.json(); // Server-side validation - can't be bypassed if (typeof price !== 'number' || price < 0 || price > 10000) { return new Response('Invalid price', { status: 400 }); } // Safe to proceed await supabase.from('products').insert({ price }); }
4. Insecure Authentication Redirects
The Problem: After OAuth login, your app redirects users. If not configured properly, attackers can redirect users to malicious sites.
https://yourapp.com/auth/callback?redirect=https://evil-site.com
The Fix:
- Whitelist allowed redirect URLs:
const ALLOWED_REDIRECTS = ['https://yourapp.com', 'https://www.yourapp.com']; function safeRedirect(url) { const isAllowed = ALLOWED_REDIRECTS.some((allowed) => url.startsWith(allowed), ); return isAllowed ? url : '/dashboard'; }
- Configure in Supabase Dashboard:
- Go to Authentication → URL Configuration
- Add only your exact production domains
- Remove
localhostbefore going live (or use a separate project)
5. Logging Sensitive Data
The Problem: During debugging, you might log everything:
// ❌ This goes to your logging service, potentially visible to employees console.log('User login:', { email, password, creditCard });
The Fix:
// ✅ Only log what you need, never sensitive data console.log('User login attempt:', { email: user.email, timestamp: new Date().toISOString(), }); // Never log: passwords, tokens, credit cards, SSNs
🛡️ The Security Checklist
Before you launch, run through this:
Environment & Keys
- All API keys are in environment variables
-
service_rolekey is ONLY used server-side -
.envfiles are in.gitignore - Production keys are different from development keys
Database
- RLS is enabled on ALL tables
- Each table has appropriate SELECT/INSERT/UPDATE/DELETE policies
- Tested: Unauthenticated requests return empty/error
Authentication
- Redirect URLs are whitelisted
- OAuth providers have production domains configured
- Session tokens expire appropriately
- Password reset flow is tested
Code
- All user inputs are validated server-side
- No sensitive data in logs
- Error messages don't leak internal details
🤔 "This Is a Lot. Where Do I Start?"
We get it. Security is complex, and getting it wrong can destroy your business.
Here's the priority order:
- 🔴 Critical (Do Today): API keys + RLS
- 🟠 High (This Week): Server-side validation
- 🟡 Medium (Before Launch): Auth redirects + logging audit
Or, let us handle it:
Security Audit Included
Every ShipTheProduct engagement includes a full security review. We check your keys, RLS policies, and auth flow before you launch.
See Pricing📚 Series Navigation
| Part | Title | Status |
|---|---|---|
| 1 | The 7-Point Deployment Checklist | ✅ |
| 2 | Web App Security Basics (This Post) | ✅ |
| 3 | 6 Performance Optimization Points for Production | 🔜 Coming Soon |
| 4 | Database Design Guide for Non-Developers | 🔜 Coming Soon |
Next up: Performance optimization—because a secure app that takes 10 seconds to load is still a failed app.
Previous: Part 1: The 7-Point Deployment Checklist
*Found a security issue in your app? Don't panic. Reach out: hello@shiptheproduct.dev*