Security Best Practices for Modern Web Apps
Security isn't a feature you add at the end. It's a mindset that shapes every architectural decision from day one. The average data breach costs $4.45 million, and for startups, a single security incident can mean the end of the business.
Yet most web applications — especially those built rapidly with AI tools or by junior developers — ship with critical vulnerabilities that any moderately skilled attacker can exploit.
This guide covers the security practices we implement at BoomInCrats DevLab on every project. These aren't theoretical — they're the exact patterns protecting real production applications.
Never Trust the Client
The most fundamental security rule in web development: anything that comes from the browser can be manipulated by the user. Forms, URLs, cookies, headers, hidden fields — all of it.
What this means in practice: all input validation must happen server-side (client-side validation is UX, not security). Prices, permissions, and sensitive data must come from your database, never from the request. User identity comes from verified auth tokens, not from request parameters. File uploads must be validated for type, size, and content.
A common mistake is trusting client-submitted prices. If a user can send a price field in their order request, they can send price: 0 and get your service for free. The fix: always look up the real price from your database server-side based on the product/package ID.
Authentication Must Be Bulletproof
Authentication is the gate to your entire application. A weak implementation means everything behind it is exposed.
Use established auth providers like Firebase Auth, Auth0, Supabase Auth, or Clerk. Don't build authentication from scratch. These handle secure password hashing, token generation and refresh, email verification, rate limiting on login attempts, OAuth provider integration, and session management.
Implement proper session management: tokens must expire (use short-lived access tokens plus refresh tokens), refresh tokens should be rotated on use, logout must invalidate sessions server-side, and sessions should be bound to the device that created them.
For any application handling sensitive data — financial, health, personal — multi-factor authentication should be available.
Authorization at Every Layer
Authentication tells you WHO the user is. Authorization tells you WHAT they're allowed to do. Both must be enforced server-side.
At the database level, every collection should have explicit restrictive rules. Users can only read their own profile. Orders are visible only to the owner and assigned partner. Writes should only happen through Cloud Functions that use the admin SDK and bypass rules — this means client-side security rules can simply deny all writes.
At the API level, every Cloud Function checks three things: is the user authenticated, is the user authorized for this action, and does the user own or have access to this specific resource.
A frontend-only admin check (checking user.role in JavaScript) is trivially bypassed. An attacker opens dev tools, changes the role value, and accesses your admin panel. Authorization must always be validated server-side.
Sanitize All Input
Every string that enters your system from a user should be sanitized before storage and before rendering.
For XSS prevention, strip or encode HTML special characters from all text input before storing in the database. Characters like angle brackets, quotes, and ampersands should be encoded into their HTML entity equivalents. This prevents attackers from injecting script tags that steal user session tokens.
For injection prevention, use parameterized queries rather than string concatenation. For Firestore, the SDK's query methods are injection-safe by design. Always validate data types before using them in queries.
For file uploads, validate the MIME type against an allowlist, enforce a maximum file size, and verify that magic bytes match the claimed MIME type. Never trust the file extension alone.
Rate Limiting Everything Sensitive
Without rate limiting, attackers can brute force passwords by trying millions of combinations, scrape your data with automated high-speed requests, perform denial of service attacks that overwhelm your infrastructure, and abuse expensive operations that run up your API costs.
Implement different rate limits by operation type. Payment operations should allow 5 requests per minute. Sensitive actions like password resets should allow 5 requests per 5 minutes. Standard CRUD operations can allow 60 requests per minute. Authentication attempts should be limited to 5 per 5 minutes.
Apply rate limiting at the very start of every Cloud Function, after auth checking but before any business logic.
Secrets Management
Never commit secrets to source control. Your .env files, service account keys, and API credentials must be in .gitignore. Use different keys for development, staging, and production environments. Rotate keys on a schedule — 90 days for API keys and 365 days for service accounts.
Frontend environment variables with prefixes like VITE_ or NEXT_PUBLIC_ are PUBLIC — they get bundled into JavaScript that anyone can read. Secret keys like payment processor secrets and AI API keys must never use these prefixes. They belong only in server-side Cloud Function environment configuration.
Secure Communication
Enforce HTTPS everywhere with HSTS headers and redirect all HTTP traffic to HTTPS. Configure CORS to only allow your own domains — never use a wildcard origin in production because it allows any website to make authenticated requests to your API.
Add a Content Security Policy header that restricts where scripts, styles, and images can load from. Even if an attacker injects HTML, CSP prevents them from loading external malicious scripts.
CSRF Protection
Cross-Site Request Forgery attacks trick authenticated users into performing actions they didn't intend. Our approach at DevLab: all client-facing mutations use Firebase callable functions (onCall), which require a Firebase Auth ID token in the Authorization header. Browsers don't attach this automatically like they do with cookies, making traditional CSRF impossible.
For any HTTP endpoints that must use cookies, add SameSite=Strict cookie attributes, custom header validation, and CSRF tokens for form submissions.
Webhook Security
When receiving webhooks from payment providers or other services, always verify the signature before processing. Compute the expected HMAC SHA256 signature from the request body using your webhook secret, then compare it to the signature in the headers using a timing-safe comparison to prevent timing attacks.
Also implement idempotency — webhooks can be delivered multiple times. Store processed event IDs and skip duplicates.
Monitoring and Incident Response
Log all authentication events including logins, logouts, failed attempts, and password changes. Log all authorization failures. Alert on anomalies like sudden spikes in failed logins or unusual access patterns. Have a documented incident response plan.
Error messages shown to users should never leak implementation details. "Invalid email or password" is correct. "No user found with email john@example.com" leaks information about which emails exist in your system.
The Security Checklist for Launch
Before shipping any web application to production, verify these items. All secrets have been removed from client code and source control. Database rules restrict access by user role with no wildcard rules. Every API endpoint validates authentication and authorization. All user input is sanitized before storage and rendering. Rate limiting exists on authentication and sensitive endpoints. HTTPS is enforced with proper headers. CORS is restricted to your domains only. File uploads are validated for type, size, and content. Webhooks verify signatures before processing. Error messages don't leak implementation details. Logging captures security events without exposing sensitive data. Dependencies have been scanned for known vulnerabilities.
How DevLab Approaches Security
Security is embedded in our development process, not added at the end. During the architecture phase, security requirements are defined alongside functional ones. During development, all writes go through Cloud Functions with input sanitization on every field and rate limiting on every endpoint. Code review is security-focused before every merge. Before launch, a comprehensive security checklist is validated. After launch, monitoring, alerting, and regular dependency updates continue.
When we complete AI-generated apps, security hardening is always the first priority. AI tools consistently produce code with missing auth checks, overly permissive database rules, and absent input validation. We fix all of this before anything goes live.
Concerned about your app's security? Get a security audit at devlab.boomincrats.com/services from our team — we find and fix vulnerabilities before attackers do.