Security
Authentication and access control via an auth provider.
Chelone gates an admin with an auth provider — an object of async methods,
paralleling the data provider. Pass it to <Admin authProvider={…}> and set
requireAuth to block the shell until the user is authenticated.
The auth provider contract
import type { CanAccessParams, UserIdentity } from "@chelone/core";
interface AuthProvider {
login(params: Record<string, unknown>): Promise<void | { redirectTo?: string }>;
logout(params?: unknown): Promise<void | string>;
checkAuth(params?: unknown): Promise<void>;
checkError(error: unknown): Promise<void>;
getIdentity?(): Promise<UserIdentity>;
getPermissions?(params?: unknown): Promise<unknown>;
canAccess?(params: CanAccessParams): Promise<boolean>;
handleCallback?(): Promise<void | { redirectTo?: string }>;
refreshAuth?(): Promise<void>;
}login / logout / checkAuth / checkError are the required four; the rest
are opt-in: getIdentity for the user menu, getPermissions for permissions,
canAccess for resource/action authorization, and handleCallback for OAuth.
refreshAuth is reserved: nothing calls it yet. Implement token refresh inside
your provider's active methods if needed.
Wiring it up
import { Admin, Resource } from "@chelone/react";
import { authProvider } from "./authProvider";
<Admin
dataProvider={dataProvider}
queryClient={queryClient}
authProvider={authProvider}
requireAuth
>
<Admin.Loading>
<p role="status">Checking your session…</p>
</Admin.Loading>
<Resource name="posts" list={PostList} />
</Admin>;With requireAuth, the shell renders the <Admin.Loading> slot while checkAuth
is pending. The layout and protected resource views do not mount during this
check. Without a loading slot, the pending state renders nothing.
If checkAuth rejects, the shell renders the built-in <LoginPage> in place at
the current URL instead of redirecting to a login route. This is the default;
replace it by adding an <Admin.LoginPage> child:
<Admin.LoginPage>
<MyLoginPage />
</Admin.LoginPage>Chelone also ships <LoginForm>, the logout action (<Logout>), the OAuth return
(<AuthCallback>), and the standalone <AuthLayout> for login-adjacent pages.
The auth hooks
Components call auth through hooks rather than the provider directly. useLogin
and useLogout are TanStack mutations (call .mutate); useAuthState and
useGetIdentity are queries (read .data):
import { useLogin, useLogout, useAuthState, useGetIdentity } from "@chelone/react";
const login = useLogin(); // login.mutate({ username, password })
const logout = useLogout(); // logout.mutate()
const { data: authState } = useAuthState(); // authState?.authenticated
const { data: identity } = useGetIdentity(); // identity?.fullNamePermissions
Implement getPermissions to expose your application's permission data. Its
shape is up to your provider. usePermissions returns it as permissions,
alongside isPending, error, and refetch:
import { usePermissions } from "@chelone/react";
const { permissions, isPending } = usePermissions<string[]>();
const isAdmin = !isPending && permissions?.includes("admin");Without an auth provider or getPermissions, usePermissions returns
permissions: undefined and isPending: false. It does not throw.
Related hooks handle authentication and access failures:
useAuthenticated()checks authentication for a page or subtree and returns{ isPending, authenticated }. Hold protected content until the check settles. It callslogouton failure by default; set{ logoutOnFailure: false }to handle failure yourself.<Authenticated>is the component form.useLogoutIfAccessDenied()returns an async callback accepting an error. It runscheckErrorand applies logout, notification, and redirect hints on rejection. It resolvestruefor an auth rejection andfalsewhencheckErrorresolves or there is no auth provider.useHandleAuthCallback()callshandleCallbackon mount and returns{ settled, error }. On success it redirects to the first available target: the returnedredirectTo, the storednextPathname,location.state.nextPathname, or/.<AuthCallback>uses this hook for the OAuth return page.
Access control
When the provider implements canAccess, useCanAccess checks whether the
current user may perform an action on a resource — the menu and action buttons
use it to hide affordances the user can't reach. It's a query whose data is a
boolean:
import { useCanAccess } from "@chelone/react";
const { data: canDelete } = useCanAccess({ resource: "posts", action: "delete" });With an auth provider that omits canAccess, useCanAccess resolves true.
Both useCanAccess and useGetIdentity throw when there is no auth provider.
With a provider that omits getIdentity, the identity query fails with an error.
Handling errors
<Admin> watches every query and mutation error on its shared QueryClient and
passes it through checkError via useLogoutIfAccessDenied. Queries keyed with
["auth", …] are skipped to avoid feedback loops.
Resolve checkError for ordinary data errors. Reject it for authentication or
access failures, such as a 401 or 403. By default, rejection logs the user out,
shows an error notification when notifications are configured, and redirects to
/login. The rejection payload can carry these hints:
redirectTo: choose a redirect target. If absent from the rejection, the original error'sredirectTois used.logoutUser: false: keep the session and show a "not authorized" notification. Redirect only whenredirectTois supplied.message: override the notification text with a string, or usefalseto suppress it. Redirects to absolute HTTP URLs also suppress the notification.
Tips
requireAuthgates the whole shell; for a public page inside an otherwise-gated app, use custom routes withnoLayout.