Access Control Provider
Access control is a broad topic with lots of advanced solutions that provide different sets of features.
Refine provides an agnostic API via the accessControlProvider
to manage access control throughout your app, which allows you to integrate different methods, such as RBAC
, ABAC
, ACL
, etc., and libraries, such as Casbin, CASL, Cerbos and AccessControl.js.
To check if a desired access will be granted, the accessControlProvider
should at least have an asynchronous method named can
with the following interface:
CanParams
: Arguments for thecan
method.CanResponse
: Return type of thecan
method.
export interface IAccessControlContext {
can?: ({ resource, action, params }: CanParams) => Promise<CanResponse>;
options?: {
buttons?: {
enableAccessControl?: boolean;
hideIfUnauthorized?: boolean;
};
queryOptions?: UseQueryOptions<CanReturnType>;
};
}
const accessControlProvider: IAccessControlContext = {
can: async ({
resource,
action,
params,
}: CanParams): Promise<CanResponse> => {
return { can: true };
},
options: {
buttons: {
enableAccessControl: true,
hideIfUnauthorized: false,
},
queryOptions: {
// ... default global query options
},
},
};
It's possible to globally configure buttons' behavior by passing options
to the accessControlProvider
.
You can still change the behavior of the buttons independently; however, if no configuration is found, buttons will fallback to configuration defined in options.buttons
.
By default, enableAccessControl
is true, hideIfUnauthorized
is false, and queryOptions
is undefined.
Usage
const App: React.FC = () => {
return (
<Refine
// other providers and props
accessControlProvider={{
can: async ({ resource, action, params }) => {
if (resource === "posts" && action === "edit") {
return {
can: false,
reason: "Unauthorized",
};
}
return { can: true };
},
options: {
buttons: {
enableAccessControl: true,
hideIfUnauthorized: false,
},
queryOptions: {
// ... default global query options
},
},
}}
>
{/* your app */}
</Refine>
);
};
Providing accessControlProvider
to the <Refine />
component won't enforce access control by itself; you will need to wrap protected routes with the <CanAccess />
component.
Refer to one of the following documentations, based on your preferred router:
Meta Access
In the can
method, you'll have access to the resource
object you passed to the <Refine/>
component.
In the example below, the can
function receives the resource
(ResourceProps) object you pass to the <Refine/>
component, which allows you to use Attribute Based Access Control (ABAC), which allows you to grant permissions based on the value of a field in the resource object.
export const accessControlProvider = {
can: async ({ resource, action, params }) => {
const resourceName = params?.resource?.name;
const anyUsefulMeta = params?.resource?.meta?.yourUsefulMeta;
if (
resourceName === "posts" &&
anyUsefulMeta === true &&
action === "edit"
) {
return {
can: false,
reason: "Unauthorized",
};
}
},
};
Using reason
property
If your response from the can
method has a reason
property, it will be shown at the tooltip of the buttons if they are disabled.
Hooks and Components
Refine provides a hook and a component to use the can
method from the accessControlProvider
.
useCan
useCan
uses the can
for the query function for react-query's useQuery
. It takes the parameters that can
takes, can be configured with queryOptions
of useQuery
and returns the result of useQuery
.
const { data } = useCan({
resource: "resource-you-ask-for-access",
action: "action-type-on-resource",
params: { foo: "optional-params" },
queryOptions: {
cacheTime: 5000,
// ... other query options
},
});
const useCan: ({
action,
resource,
params,
queryOptions,
}: CanParams) => UseQueryResult<CanReturnType*>
<CanAccess />
<CanAccess />
is a wrapper component that uses useCan
to check for access control. It takes the parameters that can
method takes and also a fallback
. If access control returns true, it renders its children; otherwise, it renders fallback
, if it was provided.
<CanAccess
resource="posts"
action="edit"
params={{ id: 1 }}
fallback={<CustomFallback />}
queryOptions={{ cacheTime: 25000 }}
>
<YourComponent />
</CanAccess>
Performance
As the number of points that check for access control in your app increases, the performance of your app may take a hit, especially if its access control involves remote endpoints. Caching the access control checks helps quite a lot, which can be done easily by configuring the staleTime
and cacheTime
properties since Refine uses react-query.
// inside your component
const { data } = useCan({
resource: "resource-you-ask-for-access",
action: "action-type-on-resource",
params: { foo: "optional-params" },
queryOptions: {
staleTime: 5 * 60 * 1000, // 5 minutes
// ... other query options
},
});
By default, Refine uses 5 minutes for cacheTime
and 0 minutes for staleTime
for its own access control points.
List of Default Access Control Points
Here is a list of components and pages Refine checks for access control:
Sider
Sider is integrated, which means that unaccessible resources won't appear in the sider menu.
Menu items will check access control with { resource, action: "list" }
. For example, if your app has a resource called posts
, it will be checked with { resource: "posts", action: "list" }
.
Buttons
These buttons will be checked for access control.
Let's say they are rendered where resource
is posts
and id
is 1
where applicable. The can
function will receive the resource
(ResourceProps) object you passed to the <Refine/>
component, which allows you to use Attribute Based Access Control (ABAC), which allows you to grant permissions based on the value of a field in the resource object.
These buttons will be disabled if access control returns { can: false }
import { EditButton, ShowButton, ListButton, CreateButton, CloneButton, DeleteButton } from "@refinedev/antd"; // or @refinedev/mui, @refinedev/chakra-ui, @refinedev/mantine
export const MyPage = () => {
return (
<>
My Page
{/* These buttons will be disabled if access control returns { can: false } */}
<ListButton resource="posts" /> {/* { resource: "posts", action: "list", params: { *resource } } */}
<CreateButton resource="posts" /> {/* { resource: "posts", action: "create", params: { *resource } } */}
<CloneButton resource="posts" recordItemId={1} /> {/* { resource: "posts", action: "create", params: { id: 1, *resource } } */}
<EditButton resource="posts" recordItemId={1} /> {/* { resource: "posts", action: "edit", params: { id: 1, *resource } } */}
<DeleteButton resource="posts" recordItemId={1} /> {/* { resource: "posts, action: "delete", params: { id: 1, *resource } } */}
<ShowButton resource="posts" recordItemId={1} /> {/* { resource: "posts", action: "show", params: { id: 1, *resource } } */}
</>
);
};
If you want to hide buttons instead of disabling them, you can pass hideIfUnauthorized: true
to the options
of the accessControlProvider
Examples
This example is for Casbin access control provider. You can check our other access control provider, Cerbos as well.
npm create refine-app@latest -- --example access-control-casbin