Authentication Flow
Relevant source files
The following files were used as context for generating this wiki page:The authentication system is responsible for managing user identity, session persistence, and access control within the application. It is built around JSON Web Tokens (JWT), specifically an access token and a refresh token. The flow handles user login, registration, logout, automatic token refreshing, and route protection based on authentication status and user roles.
State management is handled by Zustand for storing tokens, while API interactions are managed through a dedicated AuthService and a configured Axios instance. React Query hooks are used to fetch user data and manage server state related to authentication.
Core Components
The authentication feature is modular and organized by concern. The following table outlines the key components and their responsibilities.
| Component/File | Responsibility |
|---|---|
stores/auth.store.ts | A Zustand store for persisting accessToken and refreshToken in localStorage. It provides methods to set/clear tokens and derive user state (ID, role) from the JWT payload. |
features/auth/services/auth.service.ts | A service class that encapsulates all authentication-related API calls (login, register, refresh, etc.). It interacts with the api-client and the auth.store. |
lib/api-client.ts | An Axios instance with interceptors to automatically add the Authorization header to requests and handle automatic token refresh on 401 Unauthorized errors. |
features/auth/components/login-form.tsx | The UI component for user login. It captures credentials and uses authService to perform the login operation. |
components/auth/protected-route.tsx | A higher-order component that guards routes, ensuring the user is authenticated and has the required role before rendering child components. |
features/auth/hooks/use-auth.ts | A simple hook to access the authentication state (e.g., isAuthenticated) from the Zustand store. |
features/auth/hooks/use-auth-query.ts | React Query hooks (useCurrentUser, useLogin, etc.) for managing server state related to authentication. |
types/auth.types.ts | Contains TypeScript interfaces and Zod schemas for all authentication-related data structures, such as User, LoginRequest, and AuthResponse. |
Sources: README.md, stores/auth.store.ts, features/auth/services/auth.service.ts, lib/api-client.ts, components/auth/protected-route.tsx
State Management with Zustand
Authentication state, specifically the tokens, is managed globally using a Zustand store. This store persists the accessToken and refreshToken to localStorage, ensuring the user's session is maintained across page reloads.
Sources: stores/auth.store.ts
Store Structure
The auth.store is designed to hold only the tokens and the derived userId. User profile data is fetched separately via React Query.
| State Property | Type | Description |
|---|---|---|
accessToken | string | null | The JWT access token used for authenticating API requests. |
refreshToken | string | null | The JWT refresh token used to obtain a new access token when the current one expires. |
userId | string | null | The user's unique ID, extracted from the sub claim of the accessToken payload. |
Sources: stores/auth.store.ts:42-45
Key Actions and Selectors
setTokens(accessToken, refreshToken): Called after a successful login or token refresh. It decodes the newaccessTokento extract theuserIdand updates the store state.clearTokens(): Resets all auth state tonull, effectively logging the user out on the client side.isAuthenticated(): A selector that returnstrueif anaccessTokenexists.getUserId(): A selector that returns theuserIdfrom the store or decodes it from the currentaccessTokenif needed.getUserRole(): A selector that decodes theaccessTokento extract the user's role (ADMINorCUSTOMER).
The diagram below shows the state and transitions within the auth.store.
Sources: stores/auth.store.ts:51-104
API Interaction and Token Management
All communication with the backend is handled by a singleton apiClient (an Axios instance) and the AuthService. This architecture centralizes API logic and provides robust, automatic token management.
Sources: lib/api-client.ts, features/auth/services/auth.service.ts
AuthService
The AuthService class provides a clean interface for all authentication-related operations.
This diagram shows the public methods of the AuthService and its dependency on useAuthStore to manage tokens.
Sources: features/auth/services/auth.service.ts:31-131
Login Flow
The login process is initiated from the LoginForm component, which calls authService.login.
The following sequence diagram illustrates the complete login flow:
Automatic Token Refresh
The apiClient is configured with an Axios response interceptor that automatically handles token expiration. If an API request returns a 401 Unauthorized error, the interceptor attempts to get a new accessToken using the stored refreshToken.
The flow is as follows:
- An API request is made with an expired
accessToken. - The backend returns a 401 status code.
- The
apiClientresponse interceptor catches the 401 error. - It sends a request to the
/auth/refreshendpoint with therefreshToken. - If successful, the backend returns a new
accessTokenandrefreshToken. - The interceptor updates the
auth.storewith the new tokens. - The original failed request is automatically retried with the new
accessToken. - If the refresh fails, the user's tokens are cleared, and they are redirected to the login page.
This sequence diagram visualizes the token refresh process:
Sources: lib/api-client.ts:66-121
Route Protection
The <ProtectedRoute> component is used to restrict access to certain pages or layouts based on authentication status and user role.
Sources: components/auth/protected-route.tsx
Logic Flow
The component uses a useEffect hook to perform checks after the initial render, allowing the Zustand store to hydrate.
- It checks if the user is authenticated via the
useAuthhook. - If not authenticated, it redirects the user to the login page (
/auth/login), appending the current path as areturnUrlquery parameter. - If a
requiredRoleis specified, it retrieves the user's role fromuseAuthStore.getState().getUserRole(). - It compares the user's role against the required role(s).
- If the role check fails, the user is redirected to an
/unauthorizedpage. - If all checks pass, the child components are rendered.
The following flowchart illustrates the decision logic inside ProtectedRoute:
Example Usage
The AdminLayout uses ProtectedRoute to ensure only users with the ADMIN role can access the admin dashboard.
// File: app/admin/layout.tsx:48-83
export default function AdminLayout({ children }: AdminLayoutProps) {
// ...
return (
<ProtectedRoute requiredRole={UserRole.ADMIN}>
{/* Admin sidebar and content */}
{children}
</ProtectedRoute>
);
}
Sources: app/admin/layout.tsx:48-83, types/auth.types.ts:9-12
React Query Integration
For fetching user-specific data, the application uses React Query hooks. These hooks abstract the AuthService calls and manage caching, invalidation, and server state.
| Hook | Query/Mutation | Description |
|---|---|---|
useCurrentUser() | useQuery | Fetches the current user's profile from /auth/me. Caches the data for 5 minutes. |
useLogin() | useMutation | Wraps authService.login. On success, it invalidates the currentUser query to refetch user data. |
useRegister() | useMutation | Wraps authService.register. On success, it invalidates the currentUser query. |
useLogout() | useMutation | Wraps authService.logout. On success, it clears the entire React Query cache. |