canAccess
This helper function, part of the ra-rbac module, can check if the current permissions allow the user to execute an action on a resource (and optionally a record). It requires the user permissions
array, so it must be used in conjunction with usePermissions
.
Usage
canAccess
expects an object { permissions, resource, action, record }
as parameter, and returns a boolean.
import { usePermissions, EditButton } from 'react-admin';
import { canAccess } from '@react-admin/ra-rbac';
const PostEditButton = () => {
const { isPending, permissions } = usePermissions();
if (isPending) return null;
if (canAccess({ permissions, action: "edit", resource: "posts" })) {
return <EditButton />;
} else {
return null;
}
};
With the following permissions:
console.log(await authProvider.getPermissions())
// [
// { action: ['read', 'edit', 'create', 'delete'], resource: 'posts' },
// ]
The PostEditButton
component will render the <EditButton>
.
Tip: canAccess
is mostly useful when you already have the permissions at hand. If you need to fetch the permissions every time you call canAccess
, prefer using the useCanAccess
hook or the <IfCanAccess>
component instead.
Parameters
canAccess
expects a single parameter object with the following properties:
Name | Required | Type | Default | Description |
---|---|---|---|---|
permissions |
Required | Permissions[] |
- | The permissions array, as returned by the usePermissions hook. |
resource |
Required | string |
- | The resource to check, e.g. ‘users’, ‘comments’, ‘posts’, etc. |
action |
Optional | string |
- | The action to check, e.g. ‘read’, ‘list’, ‘export’, ‘delete’, etc. |
record |
Optional | object |
- | The record to check. If passed, the child only renders if the user has permissions for that record, e.g. { id: 123, firstName: "John", lastName: "Doe" } |
action
If you want to check a specific action, for instance if the user can delete
a post
, you can specify this action in the dedicated prop:
const permissions = [
{ resource: 'posts', action: ['read', 'edit', 'create', 'delete'] },
];
canAccess({ permissions, resource: 'posts', action: 'read' }); // true
canAccess({ permissions, resource: 'posts', action: 'edit' }); // true
canAccess({ permissions, resource: 'posts', action: 'create' }); // true
canAccess({ permissions, resource: 'posts', action: 'delete' }); // true
canAccess({ permissions, resource: 'posts', action: 'export' }); // false
If you just want to know whether users can access any of the resources, you don’t have to provide an action
. For instance, here’s how you may display different components depending on resources access rights in the dashboard:
import { Admin, usePermissions } from 'react-admin';
import { canAccess } from '@react-admin/ra-rbac';
import { dataProvider } from './dataProvider';
const authProvider = {
checkAuth: () => Promise.resolve(),
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
getPermissions: () =>
Promise.resolve([
{ action: 'list', resource: 'products' },
{ action: 'edit', resource: 'categories' },
]),
};
const AccessDashboard = () => {
const { permissions } = usePermissions();
return (
<>
{canAccess({
permissions,
resource: 'orders',
}) ? (
<>List of last orders...</> // no access to this component
) : null}
{canAccess({
permissions,
resource: 'products',
}) ? (
<>List of last products...</>
) : null}
{canAccess({
permissions,
resource: 'categories',
}) ? (
<>List of last categories...</>
) : null}
</>
);
};
export const MyApp = () => (
<Admin authProvider={authProvider} dataProvider={dataProvider} dashboard={AccessDashboard}>
{/*...*/}
</Admin>
);
In this example, users will see the list of last products and the list of last categories, but they won’t be able to see the list of last orders.
Note: ra-rbac’s <Resource>
component automatically checks for the list
, show
, create
and edit
actions, so you don’t actually need to use canAccess
if you want to restrict a whole resource.
import { Admin, ListGuesser, EditGuesser } from 'react-admin'; // do not import Resource here
import { Resource } from '@react-admin/ra-rbac';
import { dataProvider } from './dataProvider';
export const MyApp = () => (
<Admin authProvider={authProvider} dataProvider={dataProvider}>
<Resource name="products" list={ListGuesser} />
<Resource name="categories" list={ListGuesser} edit={EditGuesser} />
<Resource name="orders" list={ListGuesser} />
</Admin>
);
permissions
The permissions
parameter must contain the permissions of the current user. It is usually retrieved using the usePermissions
hook:
const { permissions } = usePermissions();
if (canAccess({ permissions, action: "edit", resource: "posts" })) {
return <EditButton />;
} else {
return null;
}
When the permissions
is null (e.g. if the usePermissions
hook is still loading), canAccess
always returns false
.
resource
The resource
parameter is the resource you want to check. It can be the name of a resource, or the name of a resource field, depending on the granularity you need.
const ProductList = () => {
const { isPending, permissions } = usePermissions();
if (isPending) return null;
return (
<List>
<Datagrid>
<TextField source="id" />
<TextField source="reference" />
<TextField source="width" />
<TextField source="height" />
{canAccess({
permissions,
action: 'read',
resource: 'products.price',
}) && <TextField source="price" />}
{/* this column will not render */}
{canAccess({
permissions,
action: 'read',
resource: 'products.stock',
}) && <TextField source="stock" />}
</Datagrid>
</List>
);
};
record
RBAC allows to specify record-level permissions. These permissions are checked when you specify the record
prop.
const permissions = [
{ resource: 'posts', action: 'read', record: { id: 1 } },
];
canAccess({
permissions,
resource: 'posts',
action: 'read',
record: { id: 1, title: 'Lorem Ipsum' },
}); // true
canAccess({
permissions,
resource: 'posts',
action: 'read',
record: { id: 2, title: 'Sit Dolor Amet' },
}); // false
Hiding Columns in a Datagrid
canAccess
is useful to hide restricted elements in the admin, e.g. to hide columns in a datagrid:
import { List, Datagrid, TextField } from 'react-admin';
import { canAccess } from '@react-admin/ra-rbac';
const authProvider = {
checkAuth: () => Promise.resolve(),
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
getPermissions: () => Promise.resolve({
permissions: [
{ action: 'list', resource: 'products' },
{ action: 'read', resource: 'products.price' },
],
}),
};
const ProductList = () => {
const { isPending, permissions } = usePermissions();
if (isPending) return null;
return (
<List>
<Datagrid>
<TextField source="id" />
<TextField source="reference" />
<TextField source="width" />
<TextField source="height" />
{canAccess({
permissions,
action: 'read',
resource: 'products.price',
}) && <TextField source="price" />}
{/* this column will not render */}
{canAccess({
permissions,
action: 'read',
resource: 'products.stock',
}) && <TextField source="stock" />}
</Datagrid>
</List>
);
};
Tip: Ra-rbac actually proposes a <Datagrid>
component that hides columns depending on permissions. Check the RBAC documentation for details.
Disable Menu Items Instead Of Not Showing Them
The ra-rbac
<Menu>
component does not show menu items the current user has no access to.
It is considered good security practice not to disclose to a potentially malicious user that a page exists if they are not allowed to see it.
However, you might want to disable menu items instead of not showing them.
To achieve this, you can build a custom menu with the core <Menu>
component provided by React-admin and leverage the
usePermissions
hook with the canAccess
function to disable a <Menu.Item>
Let’s take a look at the following code:
// In src/App.tsx
import { Admin, Resource } from "react-admin";
import { dataProvider } from "./dataProvider";
import {
Admin,
usePermissions,
ListGuesser,
Menu,
Layout,
Title,
} from 'react-admin';
import { canAccess, Permissions, Resource } from '@react-admin/ra-rbac';
import { Card, CardContent } from '@mui/material';
import InventoryIcon from '@mui/icons-material/Inventory';
import ClassIcon from '@mui/icons-material/Class';
import ShoppingCartCheckoutIcon from '@mui/icons-material/ShoppingCartCheckout';
const authProvider = () => ({
checkAuth: () => Promise.resolve(),
login: () => Promise.resolve(),
logout: () => Promise.resolve(),
checkError: () => Promise.resolve(),
getPermissions: () =>
promiseFor([{ action: 'list', resource: 'products' }]),
});
const MyDashboard = () => {
return (
<Card sx={{ marginTop: 5 }}>
<Title title="Welcome to the administration" />
<CardContent>Lorem ipsum sic dolor amet...</CardContent>
</Card>
);
};
const MyMenu = () => {
const { permissions } = usePermissions();
return (
<Menu>
<Menu.DashboardItem />
<Menu.Item
to="/categories"
primaryText="Categories"
leftIcon={<ClassIcon />}
disabled={
!canAccess({
permissions,
resource: 'categories',
})
}
/>
<Menu.Item
to="/products"
primaryText="Products"
leftIcon={<InventoryIcon />}
disabled={
!canAccess({
permissions,
resource: 'products',
})
}
/>
<Menu.Item
to="/orders"
primaryText="Orders"
leftIcon={<ShoppingCartCheckoutIcon />}
disabled={
!canAccess({
permissions,
resource: 'orders',
})
}
/>
</Menu>
);
};
const MyLayout = ({ children }) => (
<Layout menu={MyMenu}>
{children}
</Layout>
);
export const App = () => (
<Admin
authProvider={authProvider}
dataProvider={dataProvider}
layout={MyLayout}
dashboard={MyDashboard}
>
<Resource name="categories" list={ListGuesser} />
<Resource name="products" list={ListGuesser} />
<Resource name="orders" list={ListGuesser} />
</Admin>
);