Piplup
@piplup/react-aclComponents

HasAccess

Render content only when the current user has the required roles or permissions, with any/all matching, a fallback and a loading placeholder.

HasAccess renders its children only when the current user passes a role and permission check. It reads the user's lists from the nearest AclProvider and throws if there is none.

Use it for markup: buttons, menu items, page sections. When you need the result as a boolean, for example to filter a list or check access in an event handler, use useAcl instead.

Import

import { HasAccess } from "@piplup/react-acl";

Usage

Toggle the user's permissions and role to see how any, all, and a combined role and permission check respond.

"use client";import * as React from "react";import Alert from "@mui/material/Alert";import Checkbox from "@mui/material/Checkbox";import FormControlLabel from "@mui/material/FormControlLabel";import Stack from "@mui/material/Stack";import { AclProvider, HasAccess } from "@piplup/react-acl";const allPermissions = ["invoice:read", "invoice:write"];export default function Page() {  const [permissions, setPermissions] = React.useState<string[]>(["invoice:read"]);  const [isAdmin, setIsAdmin] = React.useState(false);  const roles = React.useMemo(() => (isAdmin ? ["admin"] : ["member"]), [isAdmin]);  const toggle = (permission: string) =>    setPermissions((current) =>      current.includes(permission)        ? current.filter((p) => p !== permission)        : [...current, permission],    );  return (    <Stack spacing={2}>      <Stack direction="row" sx={{ flexWrap: "wrap" }}>        {allPermissions.map((permission) => (          <FormControlLabel            key={permission}            label={permission}            control={              <Checkbox                checked={permissions.includes(permission)}                onChange={() => toggle(permission)}              />            }          />        ))}        <FormControlLabel          label="admin role"          control={<Checkbox checked={isAdmin} onChange={(e) => setIsAdmin(e.target.checked)} />}        />      </Stack>      <AclProvider loading={false} roles={roles} permissions={permissions}>        <Stack spacing={1}>          {/* Default mode is "any": one matching permission is enough. */}          <HasAccess            permissions={allPermissions}            fallback={<Alert severity="error">any: no invoice permission</Alert>}          >            <Alert severity="success">any: can open the invoices page</Alert>          </HasAccess>          {/* "all": every listed permission is required. */}          <HasAccess            permissions={allPermissions}            validationMode={{ permissions: "all" }}            fallback={<Alert severity="error">all: needs read and write</Alert>}          >            <Alert severity="success">all: can edit invoices</Alert>          </HasAccess>          {/* Roles and permissions must both pass. */}          <HasAccess            roles={["admin"]}            permissions={["invoice:write"]}            fallback={<Alert severity="error">admin + invoice:write: denied</Alert>}          >            <Alert severity="success">admin + invoice:write: can void invoices</Alert>          </HasAccess>        </Stack>      </AclProvider>    </Stack>  );}

What it renders

HasAccess renders exactly one of its three slots, checked in this order:

Provider stateRendersDefault
loading is trueloadingnothing
Check failsfallbacknothing
Check passeschildren—

No wrapper element is added around any of them.

How access is decided

roles and permissions are checked separately, and both must pass.

Requirementany (default)all
Prop omitted or []PassesPasses
["a"]User has aUser has a
["a", "b"]User has a or bUser has a and b

A few consequences:

  • <HasAccess> with no roles and no permissions renders its children for every user once loading is done.
  • roles={["admin"]} with no permissions checks only the role.
  • roles={["admin"]} permissions={["invoice:write"]} requires the admin role and the invoice:write permission. To allow either one, use two HasAccess blocks or useAcl.
  • Values are compared with ===, so 1 and "1" are different.

Set the mode for each list with validationMode. A key you leave out uses any:

<HasAccess
  roles={["editor", "owner"]}
  permissions={["post:publish", "post:schedule"]}
  validationMode={{ permissions: "all" }}
>
  {/* editor OR owner, AND both post:publish and post:schedule */}
  <PublishMenu />
</HasAccess>

HasAccess hides UI. It does not protect data. Anything the user must not access also needs a check on your server.

Props

Prop

Type

On this page