react-access-kit
v1.1.2
Published
Type-safe React access gates with roles, persistence, expiry, and custom prompts.
Maintainers
Readme
react-access-kit
The fastest way to ship polished, role-aware access flows in React—without committing your app to a heavyweight auth UI framework.
Drop in one component for a useful default, or take complete control with a provider, a headless hook, custom storage, and server-backed authorization. Your users get a smooth unlock flow; your team gets a small, typed API that fits the stack you already have.
react-access-kitis a presentation-layer access gate. Pair it with server-side authorization for sensitive data and actions.
Why developers choose it
| You need… | You get… |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| A gate today | A copy-paste <AccessGate> that works immediately. |
| A gate that does not look generic | Your own components through renderPrompt, or complete markup freedom with useAccessGate. |
| Several protected areas | One AccessProvider that shares access state, persistence, and timers across the app. |
| A bridge to real auth | An async authorize callback that lets your backend make the final decision. |
| A dependable developer experience | TypeScript-first APIs, accessible defaults, expiry that survives reloads, and zero runtime UI dependencies. |
Small by default. Capable when needed.
Most libraries make you choose between a rigid drop-in widget and building every detail yourself. react-access-kit gives you both paths in one focused package:
- Use the built-in prompt when speed matters.
- Swap in your design system when product polish matters.
- Go headless when the interaction is uniquely yours.
- Keep your backend in control when access has real security implications.
That means less glue code, fewer one-off dialogs, and a clean upgrade path as your application grows.
Installation
npm install react-access-kitQuick start
One component is enough for a complete access flow:
import { AccessGate } from "react-access-kit";
export function AdminArea() {
return (
<AccessGate
roles={{ admin: "demo-password" }}
role="admin"
timeoutMinutes={15}
loadingFallback={<p>Restoring access…</p>}
>
<AdminDashboard />
</AccessGate>
);
}Shared state with AccessProvider
Wrap an application area in AccessProvider when several gates should share one source of truth, timer set, and storage entry. Gates inside the provider only need a role—no prop drilling, duplicated timers, or competing storage writes.
import { AccessGate, AccessProvider } from "react-access-kit";
export function App() {
return (
<AccessProvider
roles={{ admin: "demo-password", analyst: "reports-password" }}
timeoutMinutes={30}
storageKey="my-product-access"
>
<AccessGate role="admin">
<AdminDashboard />
</AccessGate>
<AccessGate role="analyst">
<Reports />
</AccessGate>
</AccessProvider>
);
}Server-backed authorization
For sensitive capabilities, keep authorization on your server while retaining the same great component API. authorize may return a boolean or { allowed, expiresAt }; the latter lets your server control the exact session expiry.
<AccessGate
role="admin"
authorize={async ({ role, password }) => {
const response = await fetch("/api/access/unlock", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ role, password }),
});
const result = await response.json();
return {
allowed: result.allowed,
expiresAt: result.expiresAt,
};
}}
>
<AdminDashboard />
</AccessGate>Custom prompt
Use renderPrompt to integrate your design system without adapter components or styling overrides. It receives submitting as well as errors from failed passwords, missing roles, or failed authorization.
<AccessGate
roles={{ editor: "edit" }}
role="editor"
title="Editor access"
renderPrompt={({ title, onSubmit, error, submitting }) => (
<form
onSubmit={(event) => {
event.preventDefault();
const password = new FormData(event.currentTarget).get("password");
void onSubmit(String(password ?? ""));
}}
>
<h2>{title}</h2>
<input name="password" type="password" autoComplete="current-password" />
<button disabled={submitting}>
{submitting ? "Checking…" : "Continue"}
</button>
{error && <p role="alert">{error}</p>}
</form>
)}
>
<Editor />
</AccessGate>Headless API
useAccessGate is for fully custom UIs. It must be called inside an AccessProvider.
import { useAccessGate } from "react-access-kit";
function AdminUnlockButton() {
const { status, error, submitting, submit } = useAccessGate({
role: "admin",
});
if (status === "authorized") return <AdminDashboard />;
return (
<button disabled={submitting} onClick={() => void submit("demo-password")}>
{error ?? "Unlock admin"}
</button>
);
}API reference
AccessGate and AccessProvider options
AccessProvider accepts every access option below. AccessGate accepts the same options when used without a provider; inside a provider, its access options are supplied by the provider.
| Option | Type | Default | Description |
| ----------------- | ------------------------ | ------------------------- | ---------------------------------------------------------------------------------- |
| roles | Record<string, string> | {} | Local role/password map. Best suited to demos or non-sensitive UI gating. |
| role | string | required on AccessGate | Role required to render children. |
| authorize | Authorize | optional | Sync or async server-side validator. |
| persist | boolean | true | Persist unlocked roles through the configured storage adapter. |
| storage | Storage \| null | localStorage | Custom storage adapter, or null to disable browser storage. |
| storageKey | string | react-access-kit-access | Key used for persisted access. Use a unique key per app. |
| secretKey | string | built-in | Key material used to encrypt browser data. Rotating it invalidates saved sessions. |
| timeoutMinutes | number | optional | Expiry from successful local authorization. Persisted expiry survives reloads. |
| loadingFallback | ReactNode | null | Rendered while saved state is restored. |
| onSuccess | () => void | optional | Called after a successful unlock. |
| renderPrompt | function | optional | Custom prompt renderer. |
useAccess
Use useAccess for imperative flows. It returns:
| Member | Description |
| ----------------------------- | -------------------------------------------------------------------------------- |
| unlockedRoles | Currently unlocked roles. |
| hasAccess(role) | Whether the role is currently unlocked. |
| isRoleConfigured(role) | Whether a local role or an authorize callback can validate it. |
| unlock(role, password) | Synchronous local-password unlock. |
| unlockAsync(role, password) | Unlocks with authorize when configured; otherwise uses the local password map. |
| lock(role?) | Locks a role, or all roles without an argument. |
| loading | true while persisted access is being restored. |
Accessibility
The built-in PasswordPrompt provides a labeled password field, password-manager autocomplete, focus on open, disabled submission while verification is pending, and an announced error message. Custom prompts should offer equivalent semantics.
Persistence and security
Persisted access is encrypted before it is placed in browser storage. A positive timeoutMinutes stores an exact expiry timestamp and re-locks the role after refresh. Older role-array sessions remain readable for backwards compatibility.
Browser code, client-side passwords, and rendered JavaScript can be inspected by users. Do not rely on this package alone to protect sensitive data, payments, administrative actions, or API endpoints. Always enforce authentication and authorization on the server.
Development
npm test
npm run buildLicense
MIT
