The BaaS Security Challenge
Backend-as-a-Service platforms like Supabase and Firebase fundamentally change the security model of web and mobile applications. In a traditional architecture, a server-side application mediates all access to the database and external services. With BaaS, your client application communicates directly with the database, authentication service, and storage. The security boundary shifts from application code to configuration.
Traditional vs. BaaS Security Models
Traditional Architecture
Client --> Server Application --> Database
(validates, (no direct
authorizes, client access)
sanitizes)
Security is enforced in the server-side application layer. The database is not exposed to the internet.
BaaS Architecture
Client --> BaaS API (PostgREST, Firestore) --> Database
(validates JWT, (RLS/Security Rules
applies policies) enforce access)
Security is enforced by the BaaS platform through declarative rules and policies. The database is directly accessible via APIs.
Core Security Principles for BaaS
1. Assume the Client Is Compromised
Every piece of client-side code, every API key, every configuration value shipped with your app is accessible to attackers. Design your security model with this assumption.
What the attacker knows:
- Your Supabase URL and anon key
- Your Firebase config (API key, project ID)
- Your API endpoints and request formats
- Your client-side business logic
What protects you:
- RLS policies / Security Rules
- Server-side validation in Edge Functions / Cloud Functions
- Properly configured authentication
2. Defense in Depth
Never rely on a single security control. Layer your defenses:
Layer 1: Authentication (who is the user?)
Layer 2: Authorization policies (RLS / Security Rules)
Layer 3: Input validation (Edge Functions / Cloud Functions)
Layer 4: Rate limiting
Layer 5: Monitoring and alerting
Layer 6: Encrypted data at rest and in transit
3. Least Privilege
Grant the minimum permissions necessary:
-- Supabase: Grant only what's needed
REVOKE ALL ON public.orders FROM anon;
GRANT SELECT ON public.orders TO authenticated;
-- Policies further restrict to user's own data
CREATE POLICY "Users see own orders"
ON public.orders FOR SELECT
USING (auth.uid() = user_id);
// Firebase: Restrict to exact needs
match /orders/{orderId} {
allow read: if request.auth != null
&& resource.data.userId == request.auth.uid;
allow create: if request.auth != null
&& request.resource.data.userId == request.auth.uid;
// No update or delete allowed
}
Secure Architecture Patterns
Pattern 1: Client-Direct with Policies
The simplest pattern. The client accesses the database directly, protected by policies:
Client --[JWT]--> PostgREST/Firestore --[RLS/Rules]--> Data
When to use: Simple CRUD applications, user profile management, personal data.
Requirements: Comprehensive RLS policies or security rules on every table/collection.
Pattern 2: Edge Function Gateway
Sensitive operations go through a server-side function:
Client --[JWT]--> Edge/Cloud Function --[service key]--> Database
(validates, processes,
applies business logic)
When to use: Complex business logic, multi-step operations, interactions with external APIs, operations requiring the service role key.
Example: Payment processing, scan execution, report generation.
Pattern 3: Hybrid
Combine direct access for simple reads with function gateways for writes and sensitive operations:
Reads: Client --[JWT]--> PostgREST --[RLS]--> Data
Writes: Client --[JWT]--> Edge Function --[service key + validation]--> Data
This is the most common pattern in production BaaS applications and provides a good balance of performance and security.
Authentication Architecture
JWT Lifecycle
1. User signs in --> BaaS issues JWT (access_token + refresh_token)
2. Client stores tokens securely (httpOnly cookies preferred)
3. Client includes JWT in every request
4. BaaS validates JWT and applies role-based access
5. Token expires --> Client uses refresh_token to get new JWT
6. User signs out --> Refresh token revoked
Cross-Platform Considerations
- Web: Store tokens in httpOnly cookies (not localStorage)
- Mobile: Use platform secure storage (Keychain, EncryptedSharedPreferences)
- Desktop: Use OS credential manager
Multi-Tenant Security
For SaaS applications, ensure complete data isolation between tenants:
-- RLS policy for multi-tenant isolation
CREATE POLICY "Tenant isolation"
ON documents FOR ALL
USING (
organization_id IN (
SELECT org_id FROM organization_members
WHERE user_id = auth.uid()
)
)
WITH CHECK (
organization_id IN (
SELECT org_id FROM organization_members
WHERE user_id = auth.uid()
)
);
Test tenant isolation thoroughly. A single missing policy can leak data across organizations.
Security Testing Strategy
- Policy Testing: Write automated tests for every RLS policy and security rule
- Penetration Testing: Test with the anon key, authenticated as different users, cross-tenant
- Automated Scanning: Use AuditYour.app to continuously monitor your BaaS configuration
- Dependency Auditing: Keep client libraries updated and monitor for vulnerabilities
Incident Response
Prepare for security incidents before they happen:
- Know how to revoke API keys and rotate secrets
- Have monitoring in place to detect anomalous access patterns
- Maintain an audit log of all data access
- Document the process for disabling public API access in an emergency
- Practice your response procedure
Scan your app for this vulnerability
AuditYourApp automatically detects security misconfigurations in Supabase and Firebase projects. Get actionable remediation in minutes.
Run Free ScanRelated
guides
Complete Guide to Supabase Row Level Security
Deep dive into RLS policies, patterns, and common pitfalls
guides
Firebase Security Rules: The Definitive Guide
Comprehensive guide to writing secure Firebase rules
guides
Automated Security Scanning for BaaS Apps
How to integrate automated security scanning into your workflow
guides
Hardening Supabase Edge Functions
Best practices for secure Edge Function development