Portal Authentication¶
The borrower portal uses a custom AuthContext for session-based authentication, separate from the admin dashboard's react-admin auth provider.
AuthContext¶
The AuthProvider component manages authentication state:
interface AuthContextValue {
borrower: BorrowerProfile | null;
isAuthenticated: boolean; // derived from borrower !== null
isLoading: boolean; // initial session check in progress
login(email: string, password: string): Promise<void>;
logout(): Promise<void>;
}
Session Check on Mount¶
When the portal loads, the AuthProvider checks for an existing session:
- Calls
getMe()to fetch the borrower profile fromGET /api/v1/portal/me - If successful, sets the borrower in state (user is already logged in)
- If it fails (401), leaves borrower as
null - Sets
isLoading = falseafter the check completes
This enables "remember me" behavior --- if the session cookie is still valid, the user is automatically logged in.
Login Flow¶
1. ensureCsrf() → GET /api/v1/auth/session (establish CSRF cookie)
2. authApi.login(email, pass) → POST /api/v1/auth/login + X-CSRFToken
3. authApi.getMe() → GET /api/v1/portal/me
4. Set borrower in state
Logout Flow¶
1. authApi.logout() → DELETE /api/v1/auth/session + X-CSRFToken
2. Clear borrower from state
3. ProtectedRoute redirects to /login
useAuth() Hook¶
Components access auth state via the useAuth() hook:
function DashboardPage() {
const { borrower, logout } = useAuth();
return <h1>Welcome, {borrower?.first_name}</h1>;
}
Throws an error if used outside <AuthProvider>.
ProtectedRoute¶
The <ProtectedRoute> component guards all portal routes:
isLoading = true → <LoadingScreen> (spinner)
isAuthenticated = false → Navigate to /login
isAuthenticated = true → <Outlet> (render child route)
This ensures:
- No flash of content before auth check completes
- Unauthenticated users are always redirected to login
- The redirect preserves the intended URL for post-login navigation
Backend Permission¶
The backend uses the IsBorrowerUser permission class to restrict portal endpoints:
- Checks that the authenticated user has a
portal_usermapping to a borrower - All portal API data is scoped to the authenticated borrower's records
- A borrower can only see their own loans, payments, documents, etc.
Borrower-to-User Mapping¶
The Borrower model has a portal_user FK to the User model:
When a user authenticates via the portal, the backend looks up the borrower linked to that user. If no mapping exists, the user cannot access portal endpoints.
Password Reset¶
The portal includes forgot/reset password pages that use the same backend endpoints as the admin dashboard:
Forgot Password (/forgot-password):
- Collects email address
POST /api/v1/auth/password/request- Always shows success (prevents email enumeration)
Reset Password (/reset-password?token=...):
- Validates token from query parameter
- Collects new password + confirmation
POST /api/v1/auth/password/resetwith token and new password
See Also¶
- Authentication API --- Backend auth endpoints
- Pages & Routing --- Protected vs. public routes
- Borrower Management --- Portal user mapping