Authentication and Session
Introduction
This document describes the frontend authentication flow, cookie session storage, route protection, permission handling, and logout behavior.
Login Flow
The login page is src/pages/LoginPage.tsx.
High-level flow:
- Credentials: User submits email and password.
- API Call: The page calls
api.auth.login(credentials). - Data Extraction: On success, the page extracts
token,user.id, anduser.organizationId. - Session Storage: It calls
auth.setAuth(...). - Navigation: It shows success feedback and navigates to
/dashboard.
The expected login response is typed as:
interface AuthResponse {
token: string;
user: User;
}
The frontend expects the backend to return this response inside the standard API wrapper:
interface ApiResponse<T> {
success: boolean;
data: T;
errors: string[];
traceId?: string | null;
}
Cookie Storage
Session storage is managed by src/lib/auth.ts with js-cookie.
| Cookie Key | Source Constant | Purpose |
|---|---|---|
access_token | COOKIE_KEYS.ACCESS_TOKEN | Bearer token for API calls |
user_id | COOKIE_KEYS.USER_ID | Current user ID |
organization_id | COOKIE_KEYS.ORGANIZATION_ID | Current organization scope |
user_name | COOKIE_KEYS.USER_NAME | Reserved constant, not part of core auth write flow |
user_email | COOKIE_KEYS.USER_EMAIL | Reserved constant, not part of core auth write flow |
Cookie options:
| Option | Value |
|---|---|
secure | true when current page protocol is HTTPS |
sameSite | strict |
expires | 7 days |
Route Protection
PrivateRoutes in src/app/PrivateRoutes.tsx protects all /dashboard routes.
The guard uses only this check:
auth.isAuthenticated()
isAuthenticated() returns true when access_token exists in cookies. Token validity is enforced by the backend on API calls, not by decoding the token client-side.
API Authentication
src/lib/api.ts creates a centralized Axios instance. The request interceptor reads auth.getToken() and attaches:
Authorization: Bearer <token>
The response interceptor handles HTTP 401:
- Clear auth cookies.
- Redirect the browser to
/. - Reject the original request promise.
The analytics chat streaming client in src/lib/analyticsChat.ts uses fetch rather than Axios, but follows the same token rule and clears the session on 401.
Current User and Permissions
src/hooks/useApi.ts exposes useUserPermissions().
The hook:
- Checks
auth.isAuthenticated(). - Calls
api.auth.me(). - Reads permissions from
user.role.permissions. - Exposes
hasPermission(slug).
Permission slugs are centralized in src/lib/constants.ts:
| Domain | Slugs |
|---|---|
| Sensors | create-sensor, update-sensor, delete-sensor, read-sensor |
| Users | create-user, edit-user, delete-user, read-user |
| Roles | create-role, edit-role, delete-role, read-role |
| Organization | edit-organization |
Feature screens use these slugs to show or hide create, edit, and delete actions. The backend remains responsible for final authorization enforcement.
Logout Flow
Logout is triggered from the dashboard shell user menu.
Flow:
- Confirmation: Show a confirmation dialog through
showConfirm. - API Call: Call
api.auth.logout(). - Session Clearing: Clear auth cookies with
auth.clearAuth(). - Feedback: Show a success message.
- Navigation: Navigate to
/.
If the backend logout call fails, the frontend still clears local auth data and navigates to /.
Session Failure Modes
| Scenario | Frontend Behavior |
|---|---|
| Missing token before entering dashboard | Redirect to / |
API returns 401 | Clear cookies and redirect to / |
/auth/me fails in permission hook | Permission list becomes empty |
| Logout API fails | Local auth is still cleared |
| User ID cookie missing in shell | Shell skips current user fetch |
Security Notes
- The frontend stores bearer tokens in cookies, not in
localStorage. - Cookies are
sameSite: strict, reducing cross-site request risk. - The
secureflag depends on serving the app over HTTPS. - The frontend does not implement token refresh.
- The frontend does not decode JWT claims; it relies on backend endpoints for current user and permissions.