π Authentication & Routing Architecture
Last Updated: November 13, 2025
Production Ready
π― Overview
The Reformer Platform uses a dual-portal architecture with domain-based routing for authentication and user access. This document explains how users are authenticated and routed to the correct portal based on their role and domain.
ποΈ Architecture
Two Portals
- Client Portal (
my.reformer.la)- For paying clients
- Access via:
my.reformer.la/{portalSlug} - Shows onboarding checklist on home page
- No blocking - clients can access everything
- Internal Portal (
dash.reformer.la)- For employees/admins
- Access via:
dash.reformer.la/dashboard - No onboarding required
- Full admin access
π Authentication Flow
Login Process
- User submits login form (
/login)- Email and password authentication via Supabase Auth
- Backend API:
POST /api/auth/login - Returns:
{ user: {...}, session: { access_token, ... } }
- Token stored in
localStorageasauth_token - Domain-based routing determines where user goes next
πΊοΈ Routing Logic
Client Portal (my.reformer.la)
Flow: Login β Get portal slug β Redirect to my.reformer.la/{slug}
Logic:
- Check if
hostname === 'my.reformer.la' - After login, fetch
/api/auth/meto get member data - Get
member.portal_slugfrom API response - If slug exists:
- Redirect to
https://my.reformer.la/{slug}(ClientPortal component) - Client sees onboarding checklist on home page
- Redirect to
- If no slug:
- Check if user is employee/admin/owner
- If NOT employee:
- Show error: "We cannot find your account. Please contact support@reformer.la for assistance."
- Remove auth token
- Stay on login page
- If IS employee:
- Redirect to
https://dash.reformer.la/dashboard
- Redirect to
Internal Portal (dash.reformer.la)
Flow: Login β Navigate to /dashboard
Logic:
- Check if
hostname === 'dash.reformer.la' - After login, navigate to
/dashboard - No portal slug check needed
- No onboarding blocking
- Direct access to ClientDashboardHome component
π₯ User Roles
Employee/Admin
Role Check:
const isEmployee = member?.custom_fields?.role === 'employee' ||
member?.customFields?.role === 'employee' ||
member?.custom_fields?.role === 'admin' ||
member?.customFields?.role === 'admin' ||
member?.custom_fields?.role === 'owner' ||
member?.customFields?.role === 'owner'
Access:
- Can login on either portal
- On
my.reformer.la: Redirected todash.reformer.la/dashboard - On
dash.reformer.la: Go directly to/dashboard - No onboarding required
- Full admin access
Client
Access:
- Must have
portal_slugin database - Login on
my.reformer.laβ Redirected tomy.reformer.la/{slug} - See onboarding checklist on client portal home page
- Can access all client routes without blocking
π API Response Structure
/api/auth/me Response
{
"user": {
"id": "uuid",
"email": "user@example.com",
"email_confirmed_at": "timestamp",
"created_at": "timestamp"
},
"member": {
"id": "uuid",
"email": "user@example.com",
"portal_slug": "client-slug", // From accounts.portal_slug (JOIN)
"dashboard_slug": "client-slug",
"custom_fields": {
"role": "employee" | "admin" | "owner" | null
},
"onboarding_completed_at": "timestamp" | null,
"company_name": "Company Name" | null
}
}
π« Onboarding Logic
No Blocking
Important: There is NO onboarding blocking in the system.
- Clients see onboarding checklist on client portal home page
- Employees/admins have no onboarding requirement
- All routes are accessible without onboarding checks
RequireOnboardingcomponent is now a pass-through
π§ͺ Testing
Test Scenarios
- Client Login on Client Portal
- Login on
my.reformer.la - Should redirect to
my.reformer.la/{portalSlug} - Should see client portal with onboarding checklist
- Login on
- Employee Login on Client Portal
- Login on
my.reformer.laas employee - Should redirect to
dash.reformer.la/dashboard - Should see internal dashboard
- Login on
- Employee Login on Internal Portal
- Login on
dash.reformer.laas employee - Should go to
/dashboard - Should see internal dashboard
- Login on
- Client Without Portal Slug
- Login on
my.reformer.lawithout portal slug - Should show error message
- Should stay on login page
- Should remove auth token
- Login on
π Code Locations
Frontend
- Login Component:
dashboard/src/pages/Login.jsx - Client Portal:
dashboard/src/pages/ClientPortal.jsx - Internal Dashboard:
dashboard/src/pages/ClientDashboardHome.jsx - Auth Context:
dashboard/src/context/AuthContext.jsx - RequireOnboarding:
dashboard/src/components/RequireOnboarding.jsx(pass-through)
Backend
- Auth API:
reformer-platform/src/api/auth.jsPOST /api/auth/login- Login endpointGET /api/auth/me- Get current userPOST /api/auth/signup- Signup endpointPOST /api/auth/logout- Logout endpoint
π Troubleshooting
Issue: Client can't login
Symptoms: Error: "We cannot find your account", Stays on login page
Solutions:
- Check if client has
portal_slugin database - Check if client is marked as employee (should not be)
- Check if account exists in
accountstable - Check if member is linked to account (
member.account_id)
Issue: Employee redirected incorrectly
Symptoms: Employee redirected to client portal instead of internal portal
Solutions:
- Check if
custom_fields.roleis set to'employee','admin', or'owner' - Check if employee has
portal_slug(should redirect to internal portal) - Verify domain detection logic
π― Summary
The authentication and routing system provides:
- Dual-portal support - Client portal and internal portal
- Domain-based routing - Automatic routing based on hostname
- Role-based access - Employee/admin vs client
- Error handling - Clear error messages for missing accounts
- No blocking - Clients can access everything, see checklist on home page
- Employee support - Employees can login on either portal, redirected appropriately
Status: β Production Ready
Last Updated: November 13, 2025