Security & Trust
Authentication, authorization, data protection & compliance
Security is layered across every tier of the stack — from Clerk authentication at the edge to Row-Level Security in Postgres to encrypted OAuth tokens at rest.
┌─────────────────────────────────────────────────────────┐
│ CLIENT (Browser) │
│ • HTTPS only • CSRF tokens │
│ • Clerk.js session • HttpOnly cookies │
└──────────────────────────┬──────────────────────────────┘
│ TLS 1.3
┌──────────────────────────▼──────────────────────────────┐
│ EDGE (Vercel + Clerk) │
│ • clerkMiddleware() • JWT verification │
│ • Rate limiting • Bot protection │
└──────────────────────────┬──────────────────────────────┘
│ Authenticated request
┌──────────────────────────▼──────────────────────────────┐
│ API ROUTES (Next.js App Router) │
│ • auth() guard • Input validation │
│ • Request signing • Error sanitization │
└──────────┬─────────────────────────────┬────────────────┘
│ │
┌──────────▼──────────┐ ┌────────────▼───────────────┐
│ SUPABASE (Postgres)│ │ FIREBASE/FIRESTORE │
│ • RLS on all tables │ │ • Security Rules │
│ • Encrypted at rest │ │ • Role-based access │
│ • SSL connections │ │ • Field-level validation │
└──────────────────────┘ └────────────────────────────┘Authentication
All authentication is handled by Clerk — a SOC 2 Type II certified auth provider.
// middleware.ts
import { clerkMiddleware } from "@clerk/nextjs/server";
export default clerkMiddleware();
// API route protection
import { auth } from "@clerk/nextjs/server";
export async function POST(req: Request) {
const { userId } = await auth();
if (!userId) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
// ... authenticated logic
}Row-Level Security
Every Supabase table has RLS enabled. Users can only access their own data — even if application logic has a bug, Postgres enforces the boundary.
-- Users can only see their own connected accounts ALTER TABLE connected_mail_accounts ENABLE ROW LEVEL SECURITY; CREATE POLICY "Users can manage own accounts" ON connected_mail_accounts FOR ALL USING (user_id = auth.uid()) WITH CHECK (user_id = auth.uid()); -- Service role bypasses RLS for Inngest background jobs -- This is the ONLY elevated access path
RLS is our last line of defense. Even if an API route is compromised, the database itself prevents cross-tenant data access. This is the same pattern used by Supabase, Linear, and other security-first platforms.
Data Protection
Firestore Security Rules
Firebase/Firestore is used for real-time features. Security rules enforce role-based access with field-level validation.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users can read/write their own profile
match /users/{userId} {
allow read: if request.auth != null;
allow write: if request.auth.uid == userId;
}
// Messages require authentication
match /messages/{messageId} {
allow read: if request.auth != null
&& resource.data.participants.hasAny([request.auth.uid]);
allow create: if request.auth != null
&& request.resource.data.senderId == request.auth.uid;
}
// Hostel profiles — owners can write, anyone can read
match /hostels/{hostelId} {
allow read: if true;
allow write: if request.auth != null
&& request.auth.token.role == 'hostel';
}
}
}