Architecture
Auth Architecture
How authentication works across mobile, merchant portal, and admin dashboard.
Last updated March 19, 2026
Auth Architecture
CafeRadar uses three separate auth systems optimized for different contexts.
Mobile App -- Clerk
Provider: Clerk (React Native SDK)
Token format: RS256 JWT
Header: X-Clerk-Token
Clerk handles sign-up, sign-in, social auth (Google, Apple), and session management. The JWT 'sub' claim contains the Clerk user ID (e.g., user_2abc123).
RLS Integration
All RLS policies use (select auth.jwt()->>'sub') to get the current user ID. This works because Supabase's PostgREST passes the JWT claims to PostgreSQL.
Important: Never use auth.uid()::text with Clerk JWTs. That function returns the Supabase auth user ID, which is NULL for Clerk-authenticated requests.
Merchant Portal -- Clerk + Email OTP
Merchants authenticate via Clerk (same as mobile), then complete email OTP 2FA.
Flow:
- Clerk sign-in (JWT issued)
- API sends OTP to merchant's business email
- Merchant enters OTP code
- HMAC-signed cookie set (24h expiry)
- Subsequent requests verified via cookie + Clerk JWT
Tables: merchant_otp_codes (code, merchant_id, expires_at, used)
Admin Dashboard -- PBKDF2 Sessions
Sessions: In-memory store, 24-hour expiry
Cookie: admin_session (HttpOnly, Secure, SameSite=Strict)
The admin uses PBKDF2-SHA512 with 100k iterations and 32-byte salt for password hashing.
Edge Function Auth
Edge functions verify Clerk JWTs using the JWKS endpoint at /.well-known/jwks.json. For cron-triggered functions, the invoke_edge_function() SQL helper sends the service role key as Bearer token.
The shared _shared/auth.ts module provides:
getAuthenticatedUserId(req)-- Extract and verify Clerk user IDrequireAuth(req, bodyUserId)-- Verify JWT + body match (defense-in-depth)timingSafeEqual(a, b)-- Constant-time string comparison