Skip to main content

Authentication Overview

This project implements an OAuth2-compatible authentication system using Bearer tokens. The frontend manages authentication through a combination of a generated LoginService for API interactions, a global OpenAPI configuration for token injection, and a custom useAuth hook for state management.

The LoginService Interface

The LoginService class, located in frontend/src/client/sdk.gen.ts, serves as the primary interface for authentication-related API calls. It encapsulates the logic for acquiring tokens, verifying sessions, and managing password recovery.

Key methods include:

  • loginAccessToken(data: LoginLoginAccessTokenData): Performs an OAuth2-compatible login. It expects formData (type Body_login_login_access_token) and uses application/x-www-form-urlencoded as the media type.
  • testToken(): A POST request to /api/v1/login/test-token used to verify if the current access token is still valid.
  • recoverPassword(data: LoginRecoverPasswordData): Initiates the password recovery flow for a specific email.
  • resetPassword(data: LoginResetPasswordData): Finalizes the password reset using a request body containing the new password and a token.

Token Management and OpenAPI Configuration

The application uses localStorage to persist the access_token. The integration between the stored token and the API client is handled globally in frontend/src/main.tsx via the OpenAPI configuration object.

Token Resolution

The OpenAPI.TOKEN property is configured as an asynchronous resolver. This ensures that every request made through the generated client automatically includes the current token from localStorage in the Authorization header.

// frontend/src/main.tsx

OpenAPI.BASE = import.meta.env.VITE_API_URL
OpenAPI.TOKEN = async () => {
return localStorage.getItem("access_token") || ""
}

Base URL

The API endpoint is configured using OpenAPI.BASE, which pulls from the VITE_API_URL environment variable.

The useAuth Hook

The useAuth hook in frontend/src/hooks/useAuth.ts is the recommended way to interact with authentication state within React components. It abstracts the LoginService calls and manages the user's session state using @tanstack/react-query.

Login Flow

The login function inside useAuth calls LoginService.loginAccessToken and manually persists the resulting token to localStorage.

// frontend/src/hooks/useAuth.ts

const login = async (data: AccessToken) => {
const response = await LoginService.loginAccessToken({
formData: data,
})
localStorage.setItem("access_token", response.access_token)
}

const loginMutation = useMutation({
mutationFn: login,
onSuccess: () => {
navigate({ to: "/" })
},
onError: handleError.bind(showErrorToast),
})

User State

The hook provides a user object, which is fetched using UsersService.readUserMe. This query is only enabled if isLoggedIn() returns true (checking for the existence of a token in localStorage).

const { data: user } = useQuery<UserPublic | null, Error>({
queryKey: ["currentUser"],
queryFn: UsersService.readUserMe,
enabled: isLoggedIn(),
})

Session Lifecycle and Error Handling

The application implements automatic session termination for expired or invalid tokens. This is handled by a global error handler defined in frontend/src/main.tsx and attached to the QueryClient.

When any API request returns a 401 (Unauthorized) or 403 (Forbidden) error, the handleApiError function clears the local session and redirects the user to the login page.

// frontend/src/main.tsx

const handleApiError = (error: Error) => {
if (error instanceof ApiError && [401, 403].includes(error.status)) {
localStorage.removeItem("access_token")
window.location.href = "/login"
}
}

const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: handleApiError,
}),
mutationCache: new MutationCache({
onError: handleApiError,
}),
})

This centralized approach ensures that the application reacts consistently to authentication failures across all services, including LoginService, UsersService, and any other generated API clients.