Skip to main content
🧙‍♂️ refine grants your wishes! Please give us a ⭐️ on GitHub to keep the magic going.
Version: 4.xx.xx

Access Control Provider

Overview

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:

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

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

export interface IAccessControlContext {
can?: ({ resource, action, params }: CanParams) => Promise<CanReturnType>;
options?: {
buttons?: {
enableAccessControl?: boolean;
hideIfUnauthorized?: boolean;
};
};
}

const accessControlProvider: IAccessControlContext = {
can: async ({
resource,
action,
params,
}: CanParams): Promise<CanReturnType> => {
return { can: true };
},
options: {
buttons: {
enableAccessControl: true,
hideIfUnauthorized: false,
},
},
};

It's also possible to globally configure buttons' behaviour by passing options to the accessControlProvider. You can still change the behaviour of the buttons independently, however, if no configuration is found, buttons will fallback to configuration defined in options.buttons. By default, enableAccessControl is true and hideIfUnauthorized is false.

export interface IAccessControlContext {
can?: ({ resource, action, params }: CanParams) => Promise<CanReturnType>;
options?: {
buttons?: {
// default is true
enableAccessControl?: boolean;
// default is false
hideIfUnauthorized?: boolean;
};
};
}

const accessControlProvider: IAccessControlContext = {
can: async ({
resource,
action,
params,
}: CanParams): Promise<CanReturnType> => {
return { can: true };
},
// Global settings
options: {
buttons: {
enableAccessControl: true,
// hide action buttons if not authorized.
hideIfUnauthorized: true,
},
},
};

For more information, refer to these sections in the Interface References documentation IResourceItem, BaseKey, CanParams, CanReturnType

A basic usage would be like this:

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

Depending on your router, providing accessControlProvider to the <Refine> component won't enforce access control by itself; you may need to wrap protected routes with the <CanAccess> component.

Refer to one of the following documentations, depending on your preferred router:


TIP

You can also access the resource object directly.

In the example below, the can function receives the resource(ResourceItemProps) 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",
};
}
},
};

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.

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" },
});
const useCan: ({
action,
resource,
params,
queryOptions,
}: CanParams* & {
queryOptions?: UseQueryOptions<CanReturnType>;
}) => UseQueryResult<CanReturnType*>

For more information, refer to these sections in the Interfaces documentation: CanParams and 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 />}
>
<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
}
);
NOTE

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(ResourceItemProps) 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.

  • 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 } }

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

Examples

This example is for Casbin access control provider. You can check our other access control provider, Cerbos as well.

Run on your local
npm create refine-app@latest -- --example access-control-casbin