@gopherium/react-auth
v0.20.0
Published
Session authentication for React apps backed by gouncer's authkit.
Readme
@gopherium/react-auth
Session authentication for React apps backed by gouncer's authkit.
Entry points
@gopherium/react-authis the headless core: the auth API client,useSession,useLogout,sessionQueryKey, the slot-basedAuthGate,createAuthQueryClient,isSessionRevoked, andcatalogFor, which answers thees-ESandfr-FRcatalogues of the text domainDOMAIN.@gopherium/react-auth/adminis the user administration API client.@gopherium/react-auth/wpdsrenders the WordPress Design System UI:LoginScreen(brand prop),AccountPanel,usersMenuItem,SetupCodeStep, which asks for the one-time setup code as Jenkins' "Unlock Jenkins" does, andSetupAccountStep, the account rows of WordPress's installer with its password strength meter, andPasswordFieldwithusePasswordField, that step's password row for any form that sets a password. Import@gopherium/react-auth/wpds/style.cssalongside it.@gopherium/react-auth/wpds/usersis the Users page on WordPress DataViews:UsersPage,usersListDefaults, the view it opens on as data for godmin'suseListView,RoleSelect, the role menu with the fewest capabilities first,roleLabelOf, anderrorMessageOf, the words the list toasts a failed account write in. It is its own entry, so an app that only signs people in never resolves its peers.@gopherium/react-auth/testingis the msw harness:server,installTestEnvironment,seedSession,defaultUser, and canned handlers for the auth endpoints.
Mount
const client = createAuthQueryClient()
<QueryClientProvider client={client}>
<AuthGate loginScreen={(onLogin) => <LoginScreen brand="MyApp" onLogin={onLogin} />}>
<App />
</AuthGate>
</QueryClientProvider>Links under the form
LoginScreen, RequestResetScreen, ResetPasswordScreen and
SetPasswordScreen take links, drawn centred under the form in every
state. The labels arrive translated, and no links draws nothing. Every
link carries noopener noreferrer, plus the rel you give it.
<LoginScreen
brand="MyApp"
onLogin={onLogin}
links={[
{ label: 'Terms of service', href: 'https://example.com/terms', rel: 'terms-of-service' },
{ label: 'Privacy policy', href: 'https://example.com/privacy', rel: 'privacy-policy' },
]}
/>Setup wizard rows
SetupAccountStep shows the wizard's own rows in before, above
Name, and in after, below Your Email. They line up with the step's
rows only when they use the same hooks:
className="gopherium-setup__row"on each row.render={<div />}onField.Root, so the row adds no gap of its own.className="gopherium-setup__label"on the row's label.className="gopherium-setup__checkbox"on aCheckboxControl.className="gopherium-setup__help"on a help line under the field.
A checkbox row is a plain <div className="gopherium-setup__row">
with a Field.VisualLabel, as the step's Confirm Password row is.
<SetupAccountStep
code={code}
submitLabel="Install MyApp"
onCreated={onCreated}
before={
<Field.Root className="gopherium-setup__row" render={<div />}>
<Field.Label className="gopherium-setup__label">Site Title</Field.Label>
<div>
<Input value={title} onChange={(event) => setTitle(event.target.value)} />
</div>
</Field.Root>
}
/>Password field
An Add User form takes the step's password row with WordPress's
"Generate password" button first. labelPosition="top" stacks each
label above its field as a godmin form does, where the installer's
default side puts it beside the field. Send password.value once
password.ready is true.
const password = usePasswordField({ length: 24, userInputs: [name, email] })
<PasswordField password={password} generateFirst labelPosition="top" readOnly={sending} />required and customValidity take the shapes @wordpress/ui's
ValidatedInputControl takes. required adds "(Required)" to the
label. A message shows under the field and its meter, in red with the
warning icon, once the field is left, an invalid event fires or the
form's reportValidity() runs. While it is invalid the field has
aria-invalid and the browser holds the form back. When the app sets
a message after a send, it calls the form's reportValidity() in an
effect that runs after that render, so the message shows at once.
An Add User form leaves required out, as WordPress's Add User shows
no "(required)" by the password it always generates. It catches an
empty password with customValidity, as it does for Email and Name.
<PasswordField
password={password}
generateFirst
labelPosition="top"
customValidity={message === undefined ? undefined : { type: 'invalid', message }}
/>Users page
The app holds the view in its address with godmin's useListView, as
every other list does, and hands the page the result and its own words
as data. Its route keeps the view with validateSearch: listSearch.
const list = useListView<View>(usersListDefaults({ perPage: settings?.pageSize }))
<UsersPage
list={list}
pageSizes={settings?.pageSizes}
sizesFailed={settingsFailed}
roleLabels={{ admin: __('Administrator'), member: __('Member') }}
addLink={<Link to="/users/new" />}
headingLevel={2}
/>- An app with no page size setting passes a fixed
perPagetousersListDefaults. The list shows as loading until it has a page size orsizesFailed. - A row the server marks
editable: false, and the reader's own row, offer no action. errorMessagesgives words to the error codes only the app answers, from its server or its own transport. The codes authkit answers carry WordPress's words.columnsadds a column after Role as data: a label and, for each account id, the text, the words a screen reader hears and an optional link element. Pass its id tousersListDefaultsas well, to show it.showAvatars={false}drops the Avatar field when the site shows none.
Consumer requirements
reactand@tanstack/react-queryare peer dependencies; the app must own exactly one copy of each. With workspace or linked installs, add both to your bundler's dedupe list.- The client calls same-origin
/api/auth/*and/api/userspaths. In development, proxy/apito your backend so the__Host-session cookie is first-party. /wpdsneeds the@wordpress/uiand@wordpress/iconspeers./wpds/usersalso needs@gopherium/godmin0.31,@gopherium/gottext,@wordpress/dataviews,@wordpress/componentsand@wordpress/date, and godmin'sToasterabove the page, whose toasts show each name whole. The app loads the DataViews and components stylesheets, and@gopherium/react-auth/wpds/style.css, which clips a long email under the name and sizesRoleSelecton a phone. To translate the words of a search that finds nobody, it also loads godmin'slistChromeCatalogForintoLIST_CHROME_DOMAIN./testingloads byimportonly.installTestEnvironmentputs Node'sFileandFormDataon the global, so under jsdomnew FormData(form), which a React form action calls, andFileReaderon aFilethrow aTypeError,XMLHttpRequestsends a form or aFileas placeholder text, and aFiledoes not mix with aBlob.- The account step loads zxcvbn, about 400 kB gzipped, as its own chunk the first time it renders. Vite reports that chunk as larger than 500 kB.
License
Apache-2.0. Copyright © 2026 Manuel 'SirLouen' Camargo.
