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.