Login flow
User submits credentials
The login form POSTs to
/api/auth/login with email and password. This is a Next.js proxy route — credentials are validated against the external backend.Backend returns a JWT
On success, the backend issues a signed JWT token. The Next.js proxy intercepts the response and stores the token in an HTTP-only cookie named
token.Middleware enforces route protection
Every subsequent navigation is evaluated by
middleware.js. If the token cookie is present, the request continues. If it is missing on a protected route, the user is redirected to / (login page).The JWT is stored in an HTTP-only cookie, which means browser JavaScript — including any injected scripts — cannot read it. This is the primary defense against cross-site scripting (XSS) token theft.
Logout flow
Cookie is cleared
The proxy route deletes the
token cookie from the response. The browser drops the cookie immediately.Route protection
Route protection is handled entirely inmiddleware.js at the edge — no extra server calls are made.
| Condition | Behavior |
|---|---|
| Unauthenticated user visits a protected route | Redirected to / |
Authenticated user visits / | Redirected to /dashboard |
Any request to /api/* | Bypasses middleware (cookie is handled in each route handler) |
token cookie:
Public routes — accessible without authentication:
| Route | Description |
|---|---|
/ | Login page |
/register | Registration page |
/forgot-password | Password reset request |
/reset-password/[token] | Password reset confirmation |
Role-based access
KilomeTracker uses four roles. Three are assignable through the UI; one is reserved.| Role | Permissions |
|---|---|
read | View all records; no create, edit, or delete |
write | Create, edit, and delete all records |
admin | Full access plus user management (/admin-users) |
root | Superuser — exists in the database only |
isAdmin is false, the page redirects to /dashboard before any admin content is rendered.
UserContext and useUser()
UserContext (src/contexts/UserContext.tsx) is the single source of truth for authentication state across the dashboard. It is mounted as the outermost provider in the dashboard layout.
| Value | Type | Description |
|---|---|---|
user | User | null | The authenticated user object, or null if not authenticated |
isLoading | boolean | true while the initial /api/auth/me request is in flight |
isAdmin | boolean | true when the user’s role is admin or root |
isRoot | boolean | true when the user’s role is root |
isAuthenticated | boolean | true when user is non-null |
error | string | null | Error message if the /api/auth/me request fails, otherwise null |
refreshUser() | () => Promise<void> | Re-fetches the current user from /api/auth/me |
isAdmin returns true for both admin and root roles, so admin UI checks do not need to handle root separately.