Skip to main content
Version: 4.xx.xx

Access Control Provider

Access control is a broad topic where there are lots of advanced solutions that provide different set of features. refine is deliberately agnostic for its own API to be able to integrate different methods (RBAC, ABAC, ACL, etc.) and different libraries (Casbin, CASL, Cerbos, AccessControl.js). can method would be the entry point for those solutions.

refine provides an agnostic API via the accessControlProvider to manage access control throughout your app.

An accessControlProvider must implement only one async method named can to be used to check if the desired access will be granted.

can must have the interface:

type CanParams = {
resource: string;
action: string;
params?: {
resource?: IResourceItem;
id?: BaseKey;
[key: string]: any;
};
};

type CanReturnType = {
can: boolean;
reason?: string;
}

const accessControlProvider = {
can: ({ resource, action, params }: CanParams) => Promise<CanReturnType>;
}

*: Too see &#8594 IResourceItem, BaseKey, CanParams, CanReturnType

Usage

A basic example looks like:

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 };
},
}}
>
{/* your app */}
</Refine>
);
};
caution

Providing accessControlProvider to <Refine> component won't enforce access control alone. Depends on your router, you need to wrap protected routes with <CanAccess> component.

See the documentation for how to handle with different routers:

React Router Access Control

NextJS Router Access Control

Remix Router Access Control


tip

You can also access resource object directly.

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",
};
}
},
};

*resource: &#8594 It returns the resource (ResourceItemProps) object you gave to <Refine /> component. This will enable Attribute Based Access Control (ABAC), for example granting permissions based on the value of a field in the resource object.

tip

You can pass a reason along with can. It will be accessible using useCan. It will be shown at the tooltip of the buttons from refine when they are disabled.

tip

You can find access control examples made with refine

refine checks for access control in its related components and pages. Refer here to see all the places refine checks for access control.

Hooks and Components

refine provides a hook and a component to use the can method from the accessControlProvider.

useCan

useCan uses the can as the query function for react-query's useQuery. It takes the parameters that can takes. It can also be configured with queryOptions for useQuery. Returns the result of useQuery.

const { data } = useCan({
resource: "resource-you-ask-for-access",
action: "action-type-on-resource",
params: { foo: "optional-params" },
});
const useCan: ({
action,
resource,
params,
queryOptions,
}: CanParams* & {
queryOptions?: UseQueryOptions<CanReturnType>;
}) => UseQueryResult<CanReturnType*>

*: Too see &#8594 CanParams, 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. It renders its children if the access control returns true and if access control returns false renders fallback if provided.

<CanAccess
resource="posts"
action="edit"
params={{ id: 1 }}
fallback={<CustomFallback />}
>
<YourComponent />
</CanAccess>

Performance

As the number of points that checks 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 a great deal. Since refine uses react-query it can be easily done configuring staleTime and cacheTime properties.

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

refine uses 5 minutes cacheTime and 0 for staleTime by default for its own access control points.

List of Default Access Control Points

Sider

Sider is also integrated so 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 resource posts it will be checked with { resource: "posts", action: "list" }

Buttons

These buttons will check for access control. Let's say these buttons are rendered where resource is posts and id is 1 where applicable.

  • List: { resource: "posts", action: "list", params: { *resource } }
  • Create: { resource: "posts", action: "create", params: { *resource } }
  • Clone: { resource: "posts", action: "create", params: { id: 1, *resource } }
  • Edit: { resource: "posts", action: "edit", params: { id: 1, *resource } }
  • Delete: { resource: "posts, action: "delete", params: { id: 1, *resource } }
  • Show: { resource: "posts", action: "show", params: { id: 1, *resource } }

*resource: &#8594 It returns the resource (ResourceItemProps) object you gave to <Refine /> component. This will enable Attribute Based Access Control (ABAC), for example granting permissions based on the value of a field in the resource object.

These buttons will be disabled if access control returns { can: false }

Example

RUN IN YOUR LOCAL
npm create refine-app@latest -- --example access-control-casbin