Authentication is shared across both the web and iOS platforms. Both connect to the same Supabase Auth instance with the same user accounts and the same RLS policies.
Auth flow
Middleware
The Next.js middleware (middleware.ts) runs on every request and enforces authentication on protected routes.
Public routes (no auth required)
| Route | Purpose |
|---|---|
/auth/* | Auth callbacks, password reset |
/invite/* | Device rep signup portal |
/login | Login page |
/status/* | Public patient status pages (escort links) |
Public API routes
| Route | Purpose |
|---|---|
/api/invite/accept | Accept device rep invitation |
/api/create-device-rep | Create device rep account |
/api/check-auth-status | Check if current session is valid |
Protected routes
All other routes require a valid session:- Page routes → redirect to
/loginif not authenticated - API routes → return 401 JSON response
Supabase client creation
Browser (client components)
Server (server components, route handlers)
Access levels
| Level | Scope | Typical user |
|---|---|---|
global_admin | All facilities, all data | Platform owner |
facility_admin | Own facility only | OR manager, facility director |
surgeon | Own cases + facility scorecards | Surgeon |
device_rep | Assigned cases only | Implant sales rep |
Row-level security
Every table has RLS policies enforcing facility scoping:- Users see only data within their assigned facility
- Global admins bypass facility filters via
is_global_admin() - Device reps have restricted policies limiting access to assigned cases
Session management
- Web: Sessions in HTTP-only cookies, refreshed automatically
- iOS: Tokens in Keychain (migrated from UserDefaults)
- Expiration: Based on Supabase project settings with automatic refresh
Helper functions
| Function | Description |
|---|---|
get_current_user_facility() | Returns facility_id for the authenticated user |
get_my_access_level() | Returns access_level for the current user |
is_facility_accessible(facility_id) | Checks if user can access a specific facility |
is_global_admin() | Returns true if user has global admin privileges |
Security considerations
Why RLS instead of application-level auth?
Why RLS instead of application-level auth?
RLS enforces data access at the database level, which means even if application code has a bug, users can’t access data outside their facility. This is defense-in-depth — the application also filters by
facility_id, but RLS is the ultimate safety net.How are device reps restricted?
How are device reps restricted?
Device reps have specialized RLS policies that only allow access to cases they’re assigned to (via
case_device_companies). They can’t see the full case list or other facility data.What happens when a session expires?
What happens when a session expires?
On the web, sessions are automatically refreshed via HTTP-only cookies. If refresh fails, the user is redirected to
/login. On iOS, Keychain-stored tokens are refreshed automatically by the Supabase Swift SDK.