> ## Documentation Index
> Fetch the complete documentation index at: https://docs.veloiq.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# VeloIQ React Auth and Data Providers Reference

> authProvider, accessControlProvider, and httpClient wire up JWT authentication, role-based access, and HTTP requests in your React frontend.

Three provider objects exported from `@veloiq/ui` connect your React application to the VeloIQ backend: `authProvider` handles JWT login and session management, `accessControlProvider` reads role permissions and hides or disables unauthorized UI elements, and `httpClient` is an Axios instance that attaches the JWT to every API request. All three implement the corresponding Refine provider interfaces and are passed directly to the `<Refine>` component.

## `authProvider`

`authProvider` implements Refine's `AuthProvider` interface. It communicates with the VeloIQ backend's `/auth/` endpoints and stores credentials in `localStorage`.

| Method                          | Behaviour                                                                                                                                                                                                                                                                                                             |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `login({ username, password })` | Posts to `POST /auth/login`. On success, stores the access token under the key `jm_access_token` and the user profile under `jm_user`. Also fetches and caches role permissions (`/auth/roles`) and resource permissions (`/auth/resource-permissions`) for the access control provider. Redirects to `/` on success. |
| `logout()`                      | Removes `jm_access_token`, `jm_user`, `jm_role_permissions`, and `jm_resource_permissions` from `localStorage`. Redirects to `/login`.                                                                                                                                                                                |
| `check()`                       | Returns `{ authenticated: true }` when `jm_access_token` is present; otherwise redirects to `/login`.                                                                                                                                                                                                                 |
| `getIdentity()`                 | Returns the cached user from `localStorage`, or fetches it from `GET /auth/me` using the stored token.                                                                                                                                                                                                                |
| `getPermissions()`              | Returns the `roles` array from the cached user profile.                                                                                                                                                                                                                                                               |
| `onError(error)`                | Triggers logout and redirects to `/login` on HTTP 401.                                                                                                                                                                                                                                                                |

***

## `accessControlProvider`

`accessControlProvider` implements Refine's `AccessControlProvider` interface. It mirrors the backend's three-layer permission model entirely in the browser using cached data from `localStorage` — no additional network request is made per permission check.

### Permission layers

| Layer                      | Source                                      | Description                                                                                                                                                    |
| -------------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1 — Global roles           | `jm_role_permissions` in `localStorage`     | Actions each role may perform across all resources. Fetched from `GET /auth/roles` at login. Falls back to built-in Admin/Manager/Viewer defaults when absent. |
| 2 — Model-level exceptions | `jm_resource_permissions` in `localStorage` | Per-resource action overrides set by `@model_access` on backend models. Fetched from `GET /auth/resource-permissions` at login.                                |
| 3 — Field-level            | `readRoles`/`writeRoles` in `FieldDef`      | Handled by `CanAccess` wrappers in the DynamicResource components. Not passed through `accessControlProvider.can()`.                                           |

### Built-in fallback permissions

When no cached permissions are available (e.g. before the first login), the provider falls back to:

| Role      | Allowed actions                                              |
| --------- | ------------------------------------------------------------ |
| `Admin`   | `list`, `show`, `create`, `edit`, `delete`, `clone`, `field` |
| `Manager` | `list`, `show`, `create`, `edit`, `clone`, `field`           |
| `Viewer`  | `list`, `show`, `field`                                      |

<Note>
  Admin users bypass all permission checks — the `can` function returns `{ can: true }` immediately for any user whose roles include `"admin"` (case-insensitive).
</Note>

The provider is configured with `buttons.hideIfUnauthorized: true`, so Refine automatically hides action buttons the current user cannot access.

***

## `httpClient`

`httpClient` is a pre-configured Axios instance with a request interceptor that reads `jm_access_token` from `localStorage` and sets the `Authorization: Bearer <token>` header on every outgoing request.

```typescript theme={null}
import { httpClient } from "@veloiq/ui";

// Use directly for custom API calls
const response = await httpClient.get("/custom-endpoint");
```

Pass `httpClient` to `@refinedev/simple-rest`'s data provider to ensure all Refine data operations are authenticated:

```typescript theme={null}
import dataProvider from "@refinedev/simple-rest";
import { httpClient, API_URL } from "@veloiq/ui";

dataProvider(API_URL, httpClient)
```

***

## `API_URL`

`API_URL` is the base URL constant used by the providers and the data provider. Its default value is `"/api"`.

```typescript theme={null}
export const API_URL = "/api";
```

To change the base URL, configure a proxy in your Vite config to forward `/api` requests to the backend, or replace `API_URL` with your own constant:

```typescript theme={null}
// vite.config.ts
export default {
    server: {
        proxy: {
            "/api": { target: "http://localhost:8000", rewrite: (path) => path.replace(/^\/api/, "") },
        },
    },
};
```

***

## `LoginPage`

`LoginPage` is the pre-built login page component. It renders a username/password form and delegates to `authProvider.login()`.

```tsx theme={null}
import { LoginPage } from "@veloiq/ui";

<Route path="/login" element={<LoginPage appTitle="My App" />} />
```

***

## Wiring providers in App.tsx

The following is the complete provider setup from the task-manager sample application.

```tsx theme={null}
import { Refine, Authenticated } from "@refinedev/core";
import { notificationProvider } from "@refinedev/antd";
import { BrowserRouter, Routes, Route, Outlet, Navigate } from "react-router-dom";
import routerProvider from "@refinedev/react-router-v6";
import dataProvider from "@refinedev/simple-rest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import "@refinedev/antd/dist/reset.css";

import {
    authProvider,
    accessControlProvider,
    httpClient,
    LayoutWrapper,
    LoginPage,
    AllModelsProvider,
    DynamicList,
    DynamicShow,
    DynamicCreate,
    DynamicEdit,
    generateResources,
    API_URL,
    MultiPaneLayout,
    PrimaryShowContext,
    ColorModeContextProvider,
    authSystemModels,
} from "@veloiq/ui";
import type { PrimaryShowRendererProps } from "@veloiq/ui";
import { allModuleRegistrations, allSystemModels } from "./allModels.gen";

const PrimaryShowRenderer = ({ model, id, allModels }: PrimaryShowRendererProps) => (
    <DynamicShow model={model} allModels={allModels} idOverride={String(id)} />
);

const queryClient = new QueryClient();

export default function App() {
    const allModels = [...allSystemModels, ...authSystemModels];

    const resources = [
        ...allModuleRegistrations.flatMap(({ moduleName, models }) =>
            generateResources(models, moduleName)
        ),
        ...generateResources(authSystemModels, "access_control", { moduleLabel: "Access Control" }),
    ];

    return (
        <QueryClientProvider client={queryClient}>
            <AllModelsProvider models={allModels}>
                <BrowserRouter>
                    <Refine
                        dataProvider={dataProvider(API_URL, httpClient)}
                        authProvider={authProvider}
                        accessControlProvider={accessControlProvider}
                        routerProvider={routerProvider}
                        notificationProvider={notificationProvider}
                        resources={resources}
                        options={{ syncWithLocation: true, warnWhenUnsavedChanges: true }}
                    >
                        <ColorModeContextProvider>
                            <PrimaryShowContext.Provider value={PrimaryShowRenderer}>
                                <Routes>
                                    <Route path="/login" element={<LoginPage appTitle="Task Manager" />} />
                                    <Route
                                        element={
                                            <Authenticated key="auth" redirectOnFail="/login">
                                                <LayoutWrapper appTitle="Task Manager">
                                                    <Outlet />
                                                </LayoutWrapper>
                                            </Authenticated>
                                        }
                                    >
                                        {allSystemModels.map((model) => (
                                            <Route key={model.name} path={`/${model.resource || model.name}`}>
                                                <Route index element={
                                                    <MultiPaneLayout>
                                                        <DynamicList model={model} allModels={allModels} />
                                                    </MultiPaneLayout>
                                                } />
                                                <Route path="create" element={<DynamicCreate model={model} allModels={allModels} />} />
                                                <Route path="edit/:id" element={<DynamicEdit model={model} allModels={allModels} />} />
                                                <Route path="show/:id" element={
                                                    <MultiPaneLayout>
                                                        <DynamicShow model={model} allModels={allModels} />
                                                    </MultiPaneLayout>
                                                } />
                                            </Route>
                                        ))}
                                    </Route>
                                </Routes>
                            </PrimaryShowContext.Provider>
                        </ColorModeContextProvider>
                    </Refine>
                </BrowserRouter>
            </AllModelsProvider>
        </QueryClientProvider>
    );
}
```

<Tip>
  Wrap the authenticated section in `<Authenticated redirectOnFail="/login">` so Refine redirects unauthenticated users to the login page automatically. The `authProvider.check()` function drives this check.
</Tip>

***

<CardGroup cols={2}>
  <Card title="UI Overview" icon="squares-four" href="/reference/ui-overview">
    All exports from `@veloiq/ui` at a glance.
  </Card>

  <Card title="DynamicResource" icon="layout-grid" href="/reference/ui-dynamic-resource">
    Schema-driven CRUD pages and the ModelDef type reference.
  </Card>
</CardGroup>
