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 →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>
);
};
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:
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
: → 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.
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.
You can find access control examples made with refine
- Casbin → Source Code - Demo
- Cerbos → Source Code - Demo
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 →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
: → 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
npm create refine-app@latest -- --example access-control-casbin