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>
);