How to create admin permissions from plugins
Page summary:Plugins can register admin permissions server-side using
strapi.admin.services.permission.actionProvider.registerMany(). They then check these permissions on the plugin's admin routes with theadmin::hasPermissionspolicy, and protect pages with<Page.Protect>, menu links, and components using theuseRBAChook.
When developing a Strapi plugin, you might want to create admin permissions for your plugin. By doing that you can hook in to the RBAC system of Strapi to selectively grant permissions to certain pieces of your plugin.
To create admin permissions for your Strapi plugin, you'll need to register them on the server side, check them on the plugin's admin routes, and then implement them on the admin side.
Register the permissions server side
Each individual permission has to be registered in the bootstrap function of your plugin, as follows:
- JavaScript
- TypeScript
'use strict';
const bootstrap = ({ strapi }) => {
// Register permission actions.
const actions = [
{
section: 'plugins',
displayName: 'Access the overview page',
uid: 'overview.access',
pluginName: 'my-plugin',
},
{
section: 'plugins',
displayName: 'Access the content manager sidebar',
uid: 'sidebar.access',
pluginName: 'my-plugin',
},
];
strapi.admin.services.permission.actionProvider.registerMany(actions);
};
module.exports = bootstrap;
import type { Core } from '@strapi/strapi';
const bootstrap = ({ strapi }: { strapi: Core.Strapi }) => {
// Register permission actions.
const actions = [
{
section: 'plugins',
displayName: 'Access the overview page',
uid: 'overview.access',
pluginName: 'my-plugin',
},
{
section: 'plugins',
displayName: 'Access the content manager sidebar',
uid: 'sidebar.access',
pluginName: 'my-plugin',
},
];
strapi.admin.services.permission.actionProvider.registerMany(actions);
};
export default bootstrap;
Protect the server routes
The checks described in Implement permissions on the admin panel side only hide pages and components. A user can still call your plugin's API directly. Each admin route that reads or changes data must also check the permission on the server side.
To check a permission on a route, add the admin::hasPermissions policy to the route configuration. List the required actions in its config.actions array, where each action is a permission registered in the bootstrap function:
- JavaScript
- TypeScript
'use strict';
module.exports = {
type: 'admin',
routes: [
{
method: 'GET',
path: '/overview',
handler: 'overview.find',
config: {
policies: [
{
name: 'admin::hasPermissions',
config: {
actions: ['plugin::my-plugin.overview.access'],
},
},
],
},
},
],
};
export default {
type: 'admin' as const,
routes: [
{
method: 'GET' as const,
path: '/overview',
handler: 'overview.find',
config: {
policies: [
{
name: 'admin::hasPermissions',
config: {
actions: ['plugin::my-plugin.overview.access'],
},
},
],
},
},
],
};
Each action name follows the plugin::<pluginName>.<uid> format, built from the pluginName and uid values registered in the bootstrap function. When the actions array lists several actions, the user needs all of them. If the user lacks one of them, Strapi returns a 403 Forbidden error and the controller does not run.
An admin route without the admin::hasPermissions policy is open to every authenticated admin user, whatever their role.
Do I also need the admin::isAuthenticatedAdmin policy?
No. Admin routes require an authenticated admin user by default. Strapi rejects requests without valid admin credentials with a 401 Unauthorized error before any policy runs, so you do not need to add the admin::isAuthenticatedAdmin policy to require a logged-in admin. Some core routes still include it next to admin::hasPermissions.
The admin::hasPermissions policy needs this authenticated user. Do not use it on a route that sets auth: false.
Implement permissions on the admin panel side
Before we can implement our permissions on the admin panel side we have to define them in a reusable configuration file. This file can be stored anywhere in your plugin admin code. You can do that as follows:
const pluginPermissions = {
'accessOverview': [{ action: 'plugin::my-plugin.overview.access', subject: null }],
'accessSidebar': [{ action: 'plugin::my-plugin.sidebar.access', subject: null }],
};
export default pluginPermissions;
Page permissions
Once you've created the configuration file you are ready to implement your permissions. If you've bootstrapped your plugin using the plugin SDK init command, you will have an example HomePage.tsx file. To implement page permissions you can do the following:
import { Main } from '@strapi/design-system';
import { Page } from '@strapi/strapi/admin';
import { useIntl } from 'react-intl';
import pluginPermissions from '../permissions';
import { getTranslation } from '../utils/getTranslation';
const HomePage = () => {
const { formatMessage } = useIntl();
return (
<Page.Protect permissions={pluginPermissions.accessOverview}>
<Main>
<h1>Welcome to {formatMessage({ id: getTranslation('plugin.name') })}</h1>
</Main>
</Page.Protect>
);
};
export { HomePage };
You can see how we use our permissions configuration file together with the <Page.Protect> component to require specific permissions in order to view this page.
Menu link permissions
The previous example makes sure that the permissions of a user that visits your page directly will be validated. However, you might want to remove the menu link to that page as well. To do that, you'll have to make a change to the addMenuLink implementation. You can do as follows:
import { getTranslation } from './utils/getTranslation';
import { PLUGIN_ID } from './pluginId';
import { Initializer } from './components/Initializer';
import { PluginIcon } from './components/PluginIcon';
import pluginPermissions from './permissions';
export default {
register(app) {
app.addMenuLink({
to: `plugins/${PLUGIN_ID}`,
icon: PluginIcon,
intlLabel: {
id: `${PLUGIN_ID}.plugin.name`,
defaultMessage: PLUGIN_ID,
},
Component: () => import('./pages/App'),
permissions: [
pluginPermissions.accessOverview[0],
],
});
app.registerPlugin({
id: PLUGIN_ID,
initializer: Initializer,
isReady: false,
name: PLUGIN_ID,
});
},
};
Custom permissions with the useRBAC hook
To get even more control over the permission of the admin user you can use the useRBAC hook. With this hook you can use the permissions validation just like you want, as in the following example:
import React from 'react';
import { useRBAC } from '@strapi/strapi/admin';
import pluginPermissions from '../../permissions';
const Sidebar = () => {
const {
allowedActions: { canAccessSidebar },
} = useRBAC(pluginPermissions);
if (!canAccessSidebar) {
return null;
}
return (
<div>Sidebar component</div>
);
};
export default Sidebar;