Permissions
Some applications may require fine-grained permissions to enable or disable access to certain features. Since there are many possible strategies (single role, multiple roles or rights, ACLs, etc.), react-admin delegates the permission logic to authProvider.getPermissions()
.
By default, a react-admin app only requires users to be logged in for the list, create, edit, and show pages. However, should you need to customize the views according to the users permissions, you can call the usePermissions()
hook to grab them. This works for custom pages too.
User Permissions
React-admin calls the authProvider.getPermissions()
whenever it needs the user permissions. These permissions can take the shape you want:
- a string (e.g.
'admin'
), - an array of roles (e.g.
['post_editor', 'comment_moderator', 'super_admin']
) - an object with fine-grained permissions (e.g.
{ postList: { read: true, write: false, delete: false } }
) - or even a function
The format of permissions is free because react-admin never actually uses the permissions itself. It’s up to you to use them in your code to hide or display content, redirect the user to another page, or display warnings.
Following is an example where the authProvider
stores the user’s permissions in localStorage
upon authentication, and returns these permissions when called with getPermissions
:
// in src/authProvider.js
import decodeJwt from 'jwt-decode';
export default {
login: ({ username, password }) => {
const request = new Request('https://mydomain.com/authenticate', {
method: 'POST',
body: JSON.stringify({ username, password }),
headers: new Headers({ 'Content-Type': 'application/json' }),
});
return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) {
throw new Error(response.statusText);
}
return response.json();
})
.then(({ token }) => {
const decodedToken = decodeJwt(token);
localStorage.setItem('token', token);
localStorage.setItem('permissions', decodedToken.permissions);
});
},
checkError: (error) => { /* ... */ },
checkAuth: () => {
return localStorage.getItem('token') ? Promise.resolve() : Promise.reject();
},
logout: () => {
localStorage.removeItem('token');
localStorage.removeItem('permissions');
return Promise.resolve();
},
getIdentity: () => { /* ... */ },
getPermissions: () => {
const role = localStorage.getItem('permissions');
return role ? Promise.resolve(role) : Promise.reject();
}
};
Getting User Permissions
If you need to check the permissions in any of the default react-admin views or in custom page, you can use the usePermissions()
hook:
Here is an example of a Create
view with a conditional Input based on permissions:
export const UserCreate = () => {
const { permissions } = usePermissions();
return (
<Create>
<SimpleForm
defaultValue={{ role: 'user' }}
>
<TextInput source="name" validate={[required()]} />
{permissions === 'admin' &&
<TextInput source="role" validate={[required()]} />}
</SimpleForm>
</Create>
)
}
It works in custom pages too:
// in src/MyPage.js
import * as React from "react";
import { Card } from '@mui/material';
import CardContent from '@mui/material/CardContent';
import { usePermissions } from 'react-admin';
const MyPage = () => {
const { permissions } = usePermissions();
return (
<Card>
<CardContent>Lorem ipsum sic dolor amet...</CardContent>
{permissions === 'admin' &&
<CardContent>Sensitive data</CardContent>
}
</Card>
);
}
Restricting Access to Resources or Views
Permissions can be useful to restrict access to resources or their views. To do so, you must pass a function as a child of the <Admin>
component. React-admin will call this function with the permissions returned by the authProvider
. Note that you can only provide one of such function child.
<Admin
dataProvider={dataProvider}
authProvider={authProvider}
>
{permissions => (
<>
{/* Restrict access to the edit view to admin only */}
<Resource
name="customers"
list={VisitorList}
edit={permissions === 'admin' ? VisitorEdit : null}
icon={VisitorIcon}
/>
{/* Only include the categories resource for admin users */}
{permissions === 'admin'
? <Resource name="categories" list={CategoryList} edit={CategoryEdit} icon={CategoryIcon} />
: null}
</>
)}
</Admin>
Note that the function may return as many fragments as you need.
Tip: Even if that’s possible, be careful when completely excluding a resource (like with the categories
resource in this example) as it will prevent you to reference this resource in the other resource views, too.
Restricting Access to Fields and Inputs
You might want to display some fields or inputs only to users with specific permissions. You can use the usePermissions
hook for that.
Here is an example of a Create
view with a conditional Input based on permissions:
export const UserCreate = () => {
const { permissions } = usePermissions();
return (
<Create>
<SimpleForm
defaultValue={{ role: 'user' }}
>
<TextInput source="name" validate={[required()]} />
{permissions === 'admin' &&
<TextInput source="role" validate={[required()]} />}
</SimpleForm>
</Create>
);
}
This also works inside an Edit
view with a TabbedForm
, and you can even hide a FormTab
completely:
export const UserEdit = () => {
const { permissions } = usePermissions();
return (
<Edit title={<UserTitle />}>
<TabbedForm defaultValue={{ role: 'user' }}>
<TabbedForm.Tab label="user.form.summary">
{permissions === 'admin' && <TextInput disabled source="id" />}
<TextInput source="name" validate={required()} />
</TabbedForm.Tab>
{permissions === 'admin' &&
<TabbedForm.Tab label="user.form.security">
<TextInput source="role" validate={required()} />
</TabbedForm.Tab>}
</TabbedForm>
</Edit>
);
};
What about the List
view, the Datagrid
, SimpleList
? It works there, too. And in the next example, the permissions
prop is passed down to a custom filters
selector.
import * as React from 'react';
import { List, Datagrid, ShowButton, TextField, TextInput } from 'react-admin';
const getUserFilters = (permissions) => ([
<TextInput label="user.list.search" source="q" alwaysOn />,
<TextInput source="name" />,
permissions === 'admin' ? <TextInput source="role" /> : null,
].filter(filter => filter !== null));
export const UserList = () => {
const { permissions } = usePermissions();
return (
<List filters={getUserFilters(permissions)}>
<Datagrid>
<TextField source="id" />
<TextField source="name" />
{permissions === 'admin' && <TextField source="role" />}
{permissions === 'admin' && <EditButton />}
<ShowButton />
</Datagrid>
</List>
);
};
Tip: usePermissions
is asynchronous, which means that permissions
will always be undefined
on mount. Once the authProvider.getPermissions()
promise is resolved, permissions
will be set to the value returned by the promise, and the component will re-render. This may cause surprises when using permissions
in props that are not reactive, e.g. defaultValue
:
export const UserCreate = () => {
const { permissions } = usePermissions();
return (
<Create>
<SimpleForm>
<TextInput source="name" defaultValue={
// this doesn't work: the defaultValue will always be 'user'
permissions === 'admin' ? 'admin' : 'user'
} />
</SimpleForm>
</Create>
);
}
In react-hook-form
, defaultValue
is only used on mount - changing its value after the initial render doesn’t change the default value. The solution is to delay the rendering of the input until the permissions are resolved:
export const UserCreate = () => {
const { permissions } = usePermissions();
return (
<Create>
<SimpleForm>
{permissions && <TextInput source="name" defaultValue={
permissions === 'admin' ? 'admin' : 'user'
} />}
</SimpleForm>
</Create>
);
}
Restricting Access to a Menu
What if you want to check the permissions inside a custom menu? Much like getting permissions inside a custom page, you’ll have to use the usePermissions
hook:
// in src/myMenu.js
import * as React from "react";
import { MenuItemLink, usePermissions } from 'react-admin';
const Menu = ({ onMenuClick }) => {
const { permissions } = usePermissions();
return (
<div>
<MenuItemLink to="/posts" primaryText="Posts" onClick={onMenuClick} />
<MenuItemLink to="/comments" primaryText="Comments" onClick={onMenuClick} />
{permissions === 'admin' &&
<MenuItemLink to="/custom-route" primaryText="Miscellaneous" onClick={onMenuClick} />
}
</div>
);
}
Role-Based Access Control
If you need a more complex permissions with roles and groups, principle of least privilege, record-level permissions, explicit deny and more, check the next section for the Role-Based Access Control.