@hapiboo/react-auth
v0.2.2
Published
MK13 Studio Hapiboo - React Authentication
Readme
@hapiboo/react-auth
Authentication, protected requests, session persistence, route guards, and ready-to-use React controls for the Hapiboo API. It is built on @hapiboo/react-core and uses primitives and the stylesheet from @hapiboo/react-ui.
Install
pnpm add @hapiboo/react-auth @hapiboo/react-core @hapiboo/react-uiBackend requirements
@hapiboo/react-auth is the frontend implementation of authentication logic provided by @hapiboo/module-auth. The backend application must install and configure @hapiboo/module-auth together with its required dependent modules before this package can be used. It relies on that backend module's /auth/... endpoints, token behavior, and profile contract; this package does not provide an authentication backend.
Import UI styles once and place AuthProvider below HapibooProvider.
import { HapibooProvider } from '@hapiboo/react-core';
import { AuthProvider } from '@hapiboo/react-auth';
import '@hapiboo/react-ui/styles.css';
export function App() {
return (
<HapibooProvider config={{ apiUrl: 'https://api.example.com/api/v1' }}>
<AuthProvider config={{ loginUrl: '/login', loggedUrl: '/', adminLoggedUrl: '/admin' }}>
<Router />
</AuthProvider>
</HapibooProvider>
);
}apiUrl is application-owned and may include an API prefix. AuthProvider obtains its raw ApiClient from HapibooProvider; auth endpoint paths start at /auth/....
Provider and session
AuthProvider restores the stored session on mount. Until that completes, status is initializing; it then becomes authenticated or unauthenticated.
The default LocalStorageAuthStorage uses the browser key hapiboo.auth.session. Supply MemoryAuthStorage for ephemeral sessions or an AuthStorage implementation for a custom persistence strategy. AuthStorage may be synchronous or asynchronous.
| Config | Default | Purpose |
| ---------------- | -------- | ------------------------------------------------------------------------------- |
| loginUrl | /login | Destination for unauthenticated users. |
| loggedUrl | / | Destination for non-admin users after login and when blocked by requireAdmin. |
| adminLoggedUrl | /admin | Destination for admins after login and when blocked by denyAdmin. |
useAuth() exposes the current state and operations:
| Value | Purpose |
| ---------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| status, isAuthenticated, user | Read session state. user is AuthUser \| null. |
| accessToken, refreshToken | Read current tokens, or null without a session. |
| login({ login, password }) | Logs in, fetches profile, persists the session, then navigates according to user.admin. |
| confirmAccount({ code, password }) | Confirms an account, fetches profile, persists the session, then navigates according to user.admin. |
| changePassword({ oldPassword, newPassword }) | Uses the current session email and access token, then replaces stored tokens. |
| logout() | Clears the local session. No server logout endpoint is called. |
| refresh() | Exchanges the refresh token and updates local tokens; clears the session when refresh fails. |
| checkAccount() | Fetches /auth/profile, updates the stored user, and returns an authenticated request result. |
| client | Raw ApiClient configured by HapibooProvider, for non-protected API calls. |
| authenticatedClient | Token-aware client for protected application API calls. |
| config | Fully resolved auth redirect configuration. |
AuthUser has { id, name, email, accountId, admin }. AuthSession has { accessToken, refreshToken, user }.
Ready-made controls
Controls render semantic fields, submit themselves, expose loading/error/success state, and accept presentation slots. They do not render page headings, layouts, or navigation.
import { Login } from '@hapiboo/react-auth';
import { TextLink } from '@hapiboo/react-ui';
<Login
submitLabel="Enter"
submittingLabel="Signing in..."
footerAfterAction={<TextLink href="/register">Create account</TextLink>}
/>;All controls accept className, submitLabel, submittingLabel, submitEndAdornment, successMessage, footerBeforeAction, and footerAfterAction.
| Control | Fields and behavior | Label props |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| Register | name, email as login, password; automatically logs out on mount. | nameLabel, loginLabel, passwordLabel |
| Login | login, password; creates a session and navigates by role; automatically logs out on mount. | loginLabel, passwordLabel |
| ConfirmAccount | Reads code from ?code=...; submits code and password; on success navigates according to user.admin; redirects to / without code; automatically logs out on mount. | passwordLabel |
| ForgotPassword | Submits email; success is 204 No Content; automatically logs out on mount. | emailLabel |
| RecoverPassword | Reads ?code=... as recovery; submits it with new password; redirects to / without code; automatically logs out on mount. | passwordLabel |
| ChangePassword | Requires a session; submits oldPassword and newPassword; supplies current profile email internally. | currentPasswordLabel, passwordLabel |
Automatic logout prevents a prior account session surviving a public account-entry flow. ChangePassword is intentionally excluded because it requires the active session.
Hooks
Use hooks when building a custom form. Each returns { submit, isLoading, error, isSuccess }; submit resolves to the operation result or undefined after placing normalized AuthError in error.
| Hook | submit payload |
| ---------------------- | ------------------------------ |
| useRegister() | { name, login, password } |
| useLogin() | { login, password } |
| useConfirmAccount() | { code, password } |
| useForgotPassword() | { email } |
| useRecoverPassword() | { recovery, password } |
| useChangePassword() | { oldPassword, newPassword } |
normalizeAuthError(error) converts thrown errors to { type, message, status?, data?, cause? }, where type is network, server, or unknown.
Route protection
Wrap protected content in RequireAuth. It waits for session initialization. With checkAccount, it refreshes /auth/profile once for each current access token before role-based content renders.
import { RequireAuth } from '@hapiboo/react-auth';
<RequireAuth checkAccount requireAdmin loadingFallback={<p>Loading...</p>}>
<AdminDashboard />
</RequireAuth>;| Prop | Behavior |
| ----------------- | ---------------------------------------------------------------------------------- |
| checkAccount | Refetches the profile before rendering protected children. |
| requireAdmin | Sends a user with admin === false to loggedUrl. |
| denyAdmin | Sends a user with admin === true to adminLoggedUrl. |
| loadingFallback | React node rendered while session or account check is loading; defaults to null. |
Without role options, any authenticated user is allowed through. Do not combine requireAdmin and denyAdmin: they describe contradictory page audiences. An unauthenticated session always goes to loginUrl.
Protected API requests
Use authenticatedClient for application endpoints requiring an access token. Do not pass a token yourself; its request options match core ApiRequestOptions except token is omitted.
const { authenticatedClient } = useAuth();
const result = await authenticatedClient.request<{ id: string; name: string }[]>('/farms');
if (!result.ok && result.action === 'redirect-to-authentication') {
window.location.replace('/login');
}On the first 401, the client refreshes the session and retries once. A failed refresh or a second 401 returns action: 'redirect-to-authentication'. A 403 has the same action; 410 returns action: 'ignore'. Other failures include status, message, and optional backend data.
Backend contract
| Endpoint | Request | Successful response |
| ------------------------------ | ------------------------------------------------------------- | ---------------------------------------- |
| POST /auth/user/register | { name, login, password } | { user, account } |
| POST /auth/user/confirm | { code, password } | { token, refreshToken } |
| POST /auth/password/login | { login, password } | { token, refreshToken } |
| POST /auth/password/forgot | { email } | 204 No Content |
| POST /auth/password/recover | { recovery, password } | Operation response body |
| POST /auth/password/refresh | Bearer refresh token | { token, refreshToken } |
| POST /auth/password/password | Bearer access token and { email, oldPassword, newPassword } | { token, refreshToken } |
| GET /auth/profile | Bearer access token | { id, name, email, account_id, admin } |
authEndpoints exports endpoint paths. Public request and response types are exported, including LoginRequest, RegisterRequest, ConfirmRequest, ForgotPasswordRequest, RecoverPasswordRequest, ChangePasswordRequest, TokenResponse, and ProfileResponse.
