Skip to main content

Authentication Flow

The authentication flow differs between mobile and web:

Mobile (Current)

1. User submits credentials
2. supabase.auth.signInWithPassword() verifies credentials directly
3. JWT token returned to client
4. Token stored in SecureStore (expo-secure-store)
5. Supabase JS client attaches token to all subsequent requests
6. RLS policies enforce access control at the database level

Web (Phase 2)

1. User submits credentials
2. API route validates input with Zod
3. Supabase Auth verifies credentials server-side
4. JWT token stored in httpOnly cookie
5. Subsequent requests include cookie
6. API routes verify token via requireAuth() before processing

Authorization Layers

Recipe Room uses different authorization strategies per platform:
1

Supabase RLS (Both Platforms)

Row Level Security is the primary authorization layer. Every table has RLS policies that enforce access control at the database level. This is the security backbone — even if application code has a bug, RLS prevents unauthorized data access.
2

Storage Policies (Both Platforms)

Supabase Storage policies restrict file uploads based on ownership. Users can only upload to their own folder path (matched against auth.uid()).
3

API Route Auth (Web Only — Phase 2)

Web API routes use the requireAuth utility to verify the JWT and extract the user before processing:
import { requireAuth } from '@/lib/utils/auth';

const authResult = await requireAuth(supabase);
if (!authResult.success) {
  return authResult.response; // 401 Unauthorized
}
const { user } = authResult;

Mobile vs Web Security Model

The mobile app calls Supabase directly for all reads, writes, and authentication — via RPC functions, table queries, and supabase.auth methods. There are no web API route dependencies. RLS policies are the primary security boundary.
The web app (Phase 2) will route mutations through API routes (/api/*) for an additional server-side validation layer. But for mobile, Supabase RLS + client-side Zod validation is the security model.
  • Mobile: supabase client from src/lib/supabase.ts (anon key, user JWT via SecureStore)
  • Web: lib/supabase/client.ts for browser reads, lib/supabase/server.ts for API routes (service role)
  • Never expose SUPABASE_SERVICE_ROLE_KEY to any client

Input Validation

Inputs are validated client-side using Zod schemas from the shared package. On web (Phase 2), the same schemas validate server-side in API routes:
import { recipeSchema } from '@recipe-room/shared';

const result = recipeSchema.safeParse(body);
if (!result.success) {
  // Show validation errors in the form UI
}

Error Responses (Web API — Phase 2)

Standardized error format across all web API routes:
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid email format",
    "details": { "field": "email" }
  }
}
Error utilities:
FunctionStatus Code
validationError()400
unauthorizedError()401
forbiddenError()403
notFoundError()404
conflictError()409
internalError()500